settings work

This commit is contained in:
michalcourson
2026-02-22 14:57:04 -05:00
parent f2718282c7
commit d49ac95fa2
16 changed files with 205 additions and 103 deletions

View File

View File

@ -14,6 +14,7 @@ import { useAppDispatch, useAppSelector } from './hooks';
import { store } from '../redux/main';
import { useNavigate } from 'react-router-dom';
import SettingsPage from './Settings';
import { apiFetch } from './api';
function MainPage() {
const dispatch = useAppDispatch();
@ -30,7 +31,7 @@ function MainPage() {
useEffect(() => {
const fetchMetadata = async () => {
try {
const response = await fetch('http://localhost:5010/meta');
const response = await apiFetch('meta');
const data = await response.json();
dispatch({ type: 'metadata/setAllData', payload: data });
} catch (error) {

View File

@ -5,38 +5,57 @@ import './App.css';
import TextField from '@mui/material/TextField';
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
import { apiFetch } from './api';
type AudioDevice = {
id: string;
label: string;
index: number;
name: string;
default_sample_rate: number;
channels: number;
};
type Settings = {
httpPort: string;
inputDevice: AudioDevice;
outputDevice: AudioDevice;
recordingLength: number;
outputFolder: string;
http_port: number;
input_device: AudioDevice;
output_device: AudioDevice;
recording_length: number;
save_path: string;
};
const defaultSettings: Settings = {
httpPort: '',
inputDevice: { id: '', label: '' },
outputDevice: { id: '', label: '' },
recordingLength: 0,
outputFolder: '',
http_port: 0,
input_device: { index: 0, name: '', default_sample_rate: 0, channels: 0 },
output_device: { index: 0, name: '', default_sample_rate: 0, channels: 0 },
recording_length: 0,
save_path: '',
};
const fetchAudioDevices = async (): Promise<AudioDevice[]> => {
async function fetchAudioDevices(
type: 'input' | 'output',
): 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' },
];
};
return apiFetch(`device/list?device_type=${type}`)
.then((res) => res.json())
.then((data) => data.devices as AudioDevice[])
.catch((error) => {
console.error('Error fetching audio devices:', error);
return [];
});
}
async function fetchSettings(): Promise<Settings> {
// Replace with actual backend call
// Example: return window.api.getAudioDevices();
console.log('Fetching settings from backend...');
return apiFetch('settings')
.then((res) => res.json())
.then((data) => data.settings as Settings)
.catch((error) => {
console.error('Error fetching settings:', error);
return defaultSettings;
});
}
const sendSettingsToBackend = (settings: Settings) => {
// Replace with actual backend call
@ -51,11 +70,24 @@ export default function SettingsPage() {
const navigate = useNavigate();
useEffect(() => {
fetchAudioDevices()
fetchSettings()
.then((fetchedSettings) => {
console.log('Fetched settings:', fetchedSettings);
setSettings(fetchedSettings);
return null;
})
.then(() => {
return fetchAudioDevices('input');
})
.then((devices) => {
// For demo, split devices by id
setInputDevices(devices.filter((d) => d.id.includes('in')));
setOutputDevices(devices.filter((d) => d.id.includes('out')));
setInputDevices(devices);
// console.log('Input devices:', devices);
return fetchAudioDevices('output');
})
.then((devices) => {
setOutputDevices(devices);
// console.log('Output devices:', devices);
return devices;
})
.catch((error) => {
@ -78,16 +110,16 @@ export default function SettingsPage() {
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,
}));
}
// const folder = window.prompt(
// 'Enter output folder path:',
// settings.outputFolder,
// );
// if (folder !== null) {
// setSettings((prev) => ({
// ...prev,
// outputFolder: folder,
// }));
// }
};
return (
@ -109,9 +141,15 @@ export default function SettingsPage() {
variant="standard"
type="text"
name="httpPort"
value={settings.httpPort}
value={settings.http_port}
onBlur={() => console.log('port blur')}
onChange={(e) => {
setSettings((prev) => ({ ...prev, httpPort: e.target.value }));
if (!Number.isNaN(Number(e.target.value))) {
setSettings((prev) => ({
...prev,
http_port: Number(e.target.value),
}));
}
}}
className="ml-2 text-white w-[150px]"
/>
@ -121,15 +159,24 @@ export default function SettingsPage() {
<Select
variant="standard"
name="inputDevice"
value={settings.inputDevice}
value={settings.input_device.index}
onChange={(e) => {
setSettings((prev) => ({ ...prev, inputDevice: e.target.value }));
const newDevice = inputDevices.find(
(dev) => dev.index === Number(e.target.value),
);
console.log('Selected input device index:', newDevice);
if (newDevice) {
setSettings((prev) => ({
...prev,
inputDevice: newDevice,
}));
}
}}
className="ml-2 w-64"
>
{inputDevices.map((dev) => (
<MenuItem key={dev.id} value={dev.id}>
{dev.label}
<MenuItem key={dev.index} value={dev.index}>
{dev.name}
</MenuItem>
))}
</Select>
@ -139,18 +186,23 @@ export default function SettingsPage() {
<Select
variant="standard"
name="outputDevice"
value={settings.outputDevice}
value={settings.output_device.index}
onChange={(e) => {
setSettings((prev) => ({
...prev,
outputDevice: e.target.value,
}));
const newDevice = outputDevices.find(
(dev) => dev.index === Number(e.target.value),
);
if (newDevice) {
setSettings((prev) => ({
...prev,
output_device: newDevice,
}));
}
}}
className="ml-2 w-64"
>
{outputDevices.map((dev) => (
<MenuItem key={dev.id} value={dev.id}>
{dev.label}
<MenuItem key={dev.index} value={dev.index}>
{dev.name}
</MenuItem>
))}
</Select>
@ -161,12 +213,14 @@ export default function SettingsPage() {
variant="standard"
type="text"
name="recordingLength"
value={settings.recordingLength}
value={settings.recording_length}
onChange={(e) => {
setSettings((prev) => ({
...prev,
recordingLength: e.target.value,
}));
if (!Number.isNaN(Number(e.target.value))) {
setSettings((prev) => ({
...prev,
recording_length: Number(e.target.value),
}));
}
}}
className="ml-2 w-[150px]"
/>
@ -177,8 +231,8 @@ export default function SettingsPage() {
<TextField
variant="standard"
type="text"
name="outputFolder"
value={settings.outputFolder}
name="savePath"
value={settings.save_path}
className="ml-2 w-[300px]"
/>
<button

View File

@ -0,0 +1,13 @@
const getBaseUrl = () => {
// You can store the base URL in localStorage, a config file, or state
return localStorage.getItem('baseUrl') || 'http://localhost:5010';
};
export function apiFetch(endpoint: string, options = {}) {
const url = `${getBaseUrl()}/${endpoint}`;
return fetch(url, options);
}
export function setBaseUrl(baseUrl: string) {
localStorage.setItem('baseUrl', baseUrl);
}

View File

@ -15,6 +15,7 @@ import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import AudioTrimmer from './AudioTrimer';
import { ClipMetadata } from '../../redux/types';
import { useAppDispatch, useAppSelector } from '../hooks';
import { apiFetch } from '../api';
export interface ClipListProps {
collection: string;
@ -77,19 +78,16 @@ export default function ClipList({ collection }: ClipListProps) {
payload: { collection, newMetadata },
});
try {
const response = await fetch(
'http://localhost:5010/meta/collection/clips/reorder',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: collection,
clips: newMetadata.clips,
}),
const response = await apiFetch('meta/collection/clips/reorder', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
);
body: JSON.stringify({
name: collection,
clips: newMetadata.clips,
}),
});
const data = await response.json();
console.log('handle reorder return:', data.collections);
dispatch({ type: 'metadata/setAllData', payload: data });
@ -105,7 +103,7 @@ export default function ClipList({ collection }: ClipListProps) {
type: 'metadata/deleteClip',
payload: { collection, clip: meta },
});
fetch('http://localhost:5010/meta/collection/clips/remove', {
apiFetch('meta/collection/clips/remove', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -126,7 +124,7 @@ export default function ClipList({ collection }: ClipListProps) {
type: 'metadata/moveClip',
payload: { sourceCollection: collection, targetCollection, clip: meta },
});
fetch('http://localhost:5010/meta/collection/clips/move', {
apiFetch('meta/collection/clips/move', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -147,19 +145,16 @@ export default function ClipList({ collection }: ClipListProps) {
type: 'metadata/editClip',
payload: { collection, clip: meta },
});
const response = await fetch(
'http://localhost:5010/meta/collection/clips/edit',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: collection,
clip: meta,
}),
const response = await apiFetch('meta/collection/clips/edit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
);
body: JSON.stringify({
name: collection,
clip: meta,
}),
});
await response.json();
// console.log('handle clip save return:', data.collections);
dispatch({