211 lines
6.8 KiB
TypeScript
211 lines
6.8 KiB
TypeScript
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 '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 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(() => {
|
||
const fetchMetadata = async () => {
|
||
try {
|
||
const response = await apiFetch('meta');
|
||
const data = await response.json();
|
||
dispatch({ type: 'metadata/setAllData', payload: data });
|
||
} catch (error) {
|
||
console.error('Error fetching metadata:', error);
|
||
}
|
||
};
|
||
fetchMetadata();
|
||
const intervalId = setInterval(fetchMetadata, 5000);
|
||
return () => clearInterval(intervalId);
|
||
}, [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>
|
||
);
|
||
}
|