first commit

This commit is contained in:
2026-03-26 03:26:37 -07:00
commit 38d50a814f
38 changed files with 7755 additions and 0 deletions

104
src/components/Header.tsx Normal file
View File

@@ -0,0 +1,104 @@
import React, { useState } from 'react'
import { AppBar, Toolbar, IconButton, Typography, Button, Menu, MenuItem, Box, Chip, TextField } from '@mui/material'
import MenuIcon from '@mui/icons-material/Menu'
import SettingsIcon from '@mui/icons-material/Settings'
import StorageIcon from '@mui/icons-material/Storage'
import { useServers } from '../context/ServersContext'
type HeaderProps = {
onOpenSettings?: () => void
onToggleSidebar?: () => void
searchQuery?: string
onSearchQueryChange?: (value: string) => void
searchDisabled?: boolean
}
export default function Header({ onOpenSettings, onToggleSidebar, searchQuery = '', onSearchQueryChange, searchDisabled = false }: HeaderProps) {
const { servers, activeServerId, setActiveServerId } = useServers()
const [anchor, setAnchor] = useState<HTMLElement | null>(null)
const active = servers.find((s) => s.id === activeServerId)
const activeServerLabel = active ? active.name || active.host : 'No server configured'
const handleOpen = (e: React.MouseEvent<HTMLElement>) => setAnchor(e.currentTarget)
const handleClose = () => setAnchor(null)
return (
<AppBar position="static" color="transparent" elevation={0} sx={{ mb: 0, borderBottom: '1px solid rgba(255,255,255,0.04)' }}>
<Toolbar variant="dense" sx={{ minHeight: { xs: 'auto', sm: 48 }, py: { xs: 1, sm: 0.25 }, gap: 1, flexWrap: { xs: 'wrap', sm: 'nowrap' } }}>
<IconButton onClick={() => onToggleSidebar && onToggleSidebar()} aria-label="menu" size="medium" sx={{ flexShrink: 0 }}>
<MenuIcon />
</IconButton>
<TextField
value={searchQuery}
onChange={(event) => onSearchQueryChange && onSearchQueryChange(event.target.value)}
disabled={searchDisabled}
size="small"
placeholder="Search library"
sx={{
flex: { xs: '1 1 calc(100% - 104px)', sm: 1 },
minWidth: 0,
maxWidth: { sm: 520 },
order: { xs: 1, sm: 0 },
'& .MuiInputBase-input': { fontSize: { xs: 14, sm: 13 }, py: 0.9 },
}}
/>
<IconButton onClick={() => onOpenSettings && onOpenSettings()} aria-label="settings" size="medium" sx={{ width: 40, height: 40, flexShrink: 0, order: { xs: 2, sm: 0 } }}>
<SettingsIcon sx={{ fontSize: 20 }} />
</IconButton>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, width: { xs: '100%', sm: 'auto' }, minWidth: 0, order: { xs: 3, sm: 0 } }}>
<Button
variant="text"
onClick={handleOpen}
startIcon={<StorageIcon sx={{ fontSize: 18 }} />}
sx={{
px: 0.75,
py: 0.25,
fontSize: { xs: 13, sm: 13 },
justifyContent: 'flex-start',
minWidth: 0,
flex: { xs: 1, sm: '0 1 auto' },
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
}}
>
{activeServerLabel}
</Button>
{active?.lastTest && active.lastTest.ok === false && (
<Chip label={active.lastTest.message} color="error" size="small" sx={{ maxWidth: { xs: 132, sm: 200 } }} />
)}
<Menu anchorEl={anchor} open={Boolean(anchor)} onClose={handleClose}>
{servers.length === 0 ? (
<MenuItem disabled>No servers configured</MenuItem>
) : (
servers.map((s) => (
<MenuItem
key={s.id}
selected={s.id === activeServerId}
onClick={() => {
setActiveServerId(s.id)
handleClose()
}}
>
{s.name || s.host}
</MenuItem>
))
)}
<MenuItem
onClick={() => {
handleClose()
onOpenSettings && onOpenSettings()
}}
>
Manage servers...
</MenuItem>
</Menu>
</Box>
</Toolbar>
</AppBar>
)
}