Files
ClipTrimApp/electron-ui/src/renderer/App.tsx
2026-03-01 10:23:54 -05:00

233 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { MemoryRouter as Router, Routes, Route } from 'react-router-dom';
import { useEffect, useState } from 'react';
import { Provider } from 'react-redux';
import Dialog from '@mui/material/Dialog';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import io from 'socket.io-client';
// import 'tailwindcss/tailwind.css';
import './App.css';
import ClipList from './components/ClipList';
import { useAppDispatch, useAppSelector } from './hooks';
import { store } from '../redux/main';
import { useNavigate } from 'react-router-dom';
import SettingsPage from './Settings';
import { apiFetch, getBaseUrl } from './api';
function MainPage() {
const dispatch = useAppDispatch();
const collections = useAppSelector((state) =>
state.collections.map((col) => col.name),
);
const [selectedCollection, setSelectedCollection] = useState<string>(
collections[0] || 'Uncategorized',
);
const [newCollectionOpen, setNewCollectionOpen] = useState(false);
const [newCollectionName, setNewCollectionName] = useState<string>('');
const navigate = useNavigate();
useEffect(() => {}, []);
useEffect(() => {
let newSocket: any = null;
const initializeSocket = async () => {
const baseUrl = await getBaseUrl();
newSocket = io(baseUrl);
newSocket.on('connect', () => {
console.log('Connected to WebSocket server');
});
newSocket.on('full_data', (data: any) => {
console.log('Received full_data from server:', data);
dispatch({
type: 'metadata/setAllData',
payload: { collections: data },
});
});
newSocket.on('new_clip', (data: any) => {
console.log('Received new_clips from server:', data);
dispatch({
type: 'metadata/addNewClip',
payload: { clip: data },
});
});
};
initializeSocket();
return () => {
if (newSocket) {
newSocket.off('connect');
newSocket.off('full_data');
newSocket.off('new_clip');
newSocket.disconnect();
}
};
}, [dispatch]);
useEffect(() => {
// Update selected collection if collections change
if (collections.length > 0 && !collections.includes(selectedCollection)) {
setSelectedCollection(collections[0]);
}
}, [collections, selectedCollection]);
const handleNewCollectionSave = () => {
if (
newCollectionName.trim() &&
!collections.includes(newCollectionName.trim())
) {
dispatch({
type: 'metadata/addCollection',
payload: newCollectionName.trim(),
});
setSelectedCollection(newCollectionName.trim());
fetch('http://localhost:5010/meta/collections/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: newCollectionName.trim() }),
})
.then((res) => res.json())
.catch((err) => console.error('Error creating collection:', err));
}
setNewCollectionOpen(false);
setNewCollectionName('');
};
return (
<div className="min-h-screen min-w-screen bg-midnight text-offwhite relative">
{/* Left Nav Bar - sticky */}
<Dialog
open={newCollectionOpen}
onClose={() => setNewCollectionOpen(false)}
slotProps={{
paper: { sx: { backgroundColor: '#1a1a1a', color: 'white' } },
}}
>
<DialogTitle>Edit Clip Name</DialogTitle>
<DialogContent>
<input
autoFocus
className="font-bold text-lg bg-transparent outline-none border-b border-plum focus:border-plumDark text-white mb-1 w-full text-center"
type="text"
value={newCollectionName}
onChange={(e) => setNewCollectionName(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') handleNewCollectionSave();
}}
aria-label="New collection name"
/>
</DialogContent>
<DialogActions>
<button
type="button"
onClick={() => {
setNewCollectionOpen(false);
setNewCollectionName('');
}}
className="bg-plum hover:bg-plumDark text-white font-bold h-10 px-4 rounded-md"
>
Cancel
</button>
<button
type="button"
onClick={handleNewCollectionSave}
className="bg-plum hover:bg-plumDark text-white font-bold h-10 px-4 rounded-md"
>
Save
</button>
</DialogActions>
</Dialog>
<nav
className="w-48 h-screen sticky top-0 left-0 border-r border-neutral-700 bg-midnight flex flex-col p-2"
style={{ position: 'absolute', top: 0, left: 0 }}
>
<div className="p-4 font-bold text-lg">Collections</div>
<li>
<button
type="button"
className="w-full rounded text-left px-4 py-2 mb-2 bg-plumDark text-offwhite font-semibold hover:bg-plum"
onClick={() => setNewCollectionOpen(true)}
>
+ Create Collection
</button>
</li>
<ul className="flex-1 overflow-y-auto">
{collections.map((col) => (
<li key={col}>
<button
type="button"
className={`w-full rounded text-left px-4 py-2 mt-2 hover:bg-plumDark ${selectedCollection === col ? 'bg-plum text-offwhite font-semibold' : 'text-offwhite'}`}
onClick={() => setSelectedCollection(col)}
>
{col}
</button>
</li>
))}
</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>
{/* Main Content */}
<div
className="absolute top-0 ml-[12rem] w-[calc(100%-12rem)] h-screen overflow-y-auto p-4"
// style={{ left: '12rem', width: 'calc(100% - 12rem)' }}
>
<ClipList collection={selectedCollection} />
</div>
</div>
);
}
export default function App() {
const theme = createTheme({
colorSchemes: {
light: false,
dark: {
palette: {
primary: {
main: '#6e44ba', // plum
dark: '#6e44ba', // plum
contrastText: '#ffffff',
},
secondary: {
main: '#4f3186', // plumDark
dark: '#4f3186', // plumDark
contrastText: '#ffffff',
},
},
},
},
// colorSchemes: {
// light: false,
// dark: true,
// },
});
return (
<Provider store={store}>
<ThemeProvider theme={theme}>
<Router>
<Routes>
<Route path="/" element={<MainPage />} />
<Route path="/settings" element={<SettingsPage />} />
</Routes>
</Router>
</ThemeProvider>
</Provider>
);
}