mirror of
https://github.com/chrisbenincasa/tunarr.git
synced 2026-04-18 09:03:35 -04:00
initial mobile improvements (#413)
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/favicon.svg"/>
|
||||
<link rel="apple-touch-icon" href="/favicon.svg">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tunarr</title>
|
||||
</head>
|
||||
|
||||
181
web/src/App.tsx
181
web/src/App.tsx
@@ -1,6 +1,8 @@
|
||||
import { ExpandLess, ExpandMore, GitHub, Home } from '@mui/icons-material';
|
||||
import ComputerIcon from '@mui/icons-material/Computer';
|
||||
import LinkIcon from '@mui/icons-material/Link';
|
||||
import LiveTvIcon from '@mui/icons-material/LiveTv';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import PreviewIcon from '@mui/icons-material/Preview';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import SettingsRemoteIcon from '@mui/icons-material/SettingsRemote';
|
||||
@@ -22,15 +24,24 @@ import {
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuProps,
|
||||
Toolbar,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import Container from '@mui/material/Container';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||
import {
|
||||
ThemeProvider,
|
||||
alpha,
|
||||
createTheme,
|
||||
styled,
|
||||
} from '@mui/material/styles';
|
||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import { isUndefined } from 'lodash-es';
|
||||
import { isNull, isUndefined } from 'lodash-es';
|
||||
import React, { ReactNode, useCallback, useMemo, useState } from 'react';
|
||||
import { Outlet, Link as RouterLink } from 'react-router-dom';
|
||||
import './App.css';
|
||||
@@ -51,16 +62,70 @@ interface NavItem {
|
||||
icon?: ReactNode;
|
||||
}
|
||||
|
||||
const StyledMenu = styled((props: MenuProps) => (
|
||||
<Menu
|
||||
elevation={0}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
))(({ theme }) => ({
|
||||
'& .MuiPaper-root': {
|
||||
borderRadius: 6,
|
||||
marginTop: theme.spacing(1),
|
||||
minWidth: 180,
|
||||
color:
|
||||
theme.palette.mode === 'light'
|
||||
? 'rgb(55, 65, 81)'
|
||||
: theme.palette.grey[300],
|
||||
boxShadow:
|
||||
'rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px',
|
||||
'& .MuiMenu-list': {
|
||||
padding: '4px 0',
|
||||
},
|
||||
'& .MuiMenuItem-root': {
|
||||
'& .MuiSvgIcon-root': {
|
||||
fontSize: 18,
|
||||
color: theme.palette.text.secondary,
|
||||
marginRight: theme.spacing(1.5),
|
||||
},
|
||||
'&:active': {
|
||||
backgroundColor: alpha(
|
||||
theme.palette.primary.main,
|
||||
theme.palette.action.selectedOpacity,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
export function Root({ children }: { children?: React.ReactNode }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const [sublistStates, setSublistStates] = useState<Record<string, boolean>>(
|
||||
{},
|
||||
);
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const mobileLinksOpen = !isNull(anchorEl);
|
||||
|
||||
const toggleDrawerOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const toggleDrawerClosed = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
@@ -186,6 +251,36 @@ export function Root({ children }: { children?: React.ReactNode }) {
|
||||
[open, showWelcome],
|
||||
);
|
||||
|
||||
const Links: NavItem[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
name: 'XMLTV',
|
||||
path: `${settings.backendUri}/api/xmltv.xml`,
|
||||
visible: true,
|
||||
icon: <LinkIcon />,
|
||||
},
|
||||
{
|
||||
name: 'M3U',
|
||||
path: `${settings.backendUri}/api/channels.m3u`,
|
||||
visible: true,
|
||||
icon: <LinkIcon />,
|
||||
},
|
||||
{
|
||||
name: 'GitHub',
|
||||
path: 'https://github.com/chrisbenincasa/tunarr',
|
||||
visible: true,
|
||||
icon: <GitHub />,
|
||||
},
|
||||
{
|
||||
name: 'Documentation',
|
||||
path: 'https://tunarr.com/',
|
||||
visible: true,
|
||||
icon: <TextSnippetIcon />,
|
||||
},
|
||||
],
|
||||
[mobileLinksOpen],
|
||||
);
|
||||
|
||||
const handleOpenClick = useCallback((itemName: string) => {
|
||||
setSublistStates((prev) => ({
|
||||
...prev,
|
||||
@@ -235,31 +330,62 @@ export function Root({ children }: { children?: React.ReactNode }) {
|
||||
</Typography>
|
||||
<Box flexGrow={1}></Box>
|
||||
<DarkModeButton iconOnly />
|
||||
<IconButton
|
||||
href="https://github.com/chrisbenincasa/tunarr"
|
||||
target="_blank"
|
||||
color="inherit"
|
||||
>
|
||||
<GitHub />
|
||||
</IconButton>
|
||||
<Button
|
||||
href={`${settings.backendUri}/api/xmltv.xml`}
|
||||
target="_blank"
|
||||
color="inherit"
|
||||
startIcon={<TextSnippetIcon />}
|
||||
sx={{ px: 1, ml: 0.5 }}
|
||||
>
|
||||
XMLTV
|
||||
</Button>
|
||||
<Button
|
||||
href={`${settings.backendUri}/api/channels.m3u`}
|
||||
target="_blank"
|
||||
color="inherit"
|
||||
startIcon={<TextSnippetIcon />}
|
||||
sx={{ px: 1, ml: 0.5 }}
|
||||
>
|
||||
M3U
|
||||
</Button>
|
||||
{smallViewport ? (
|
||||
<>
|
||||
<Button onClick={handleClick} color="inherit">
|
||||
<MenuIcon />
|
||||
</Button>
|
||||
<StyledMenu
|
||||
MenuListProps={{
|
||||
'aria-labelledby': 'demo-customized-button',
|
||||
}}
|
||||
anchorEl={anchorEl}
|
||||
open={mobileLinksOpen}
|
||||
onClose={handleClose}
|
||||
>
|
||||
{Links.map((link) => (
|
||||
<MenuItem
|
||||
disableRipple
|
||||
component={RouterLink}
|
||||
to={link.path}
|
||||
target="_blank"
|
||||
color="inherit"
|
||||
sx={{ px: 1, ml: 0.5 }}
|
||||
key={`mobile-${link.name}`}
|
||||
divider={link.name === 'M3U'}
|
||||
>
|
||||
{link.icon} {link.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</StyledMenu>
|
||||
</>
|
||||
) : (
|
||||
Links.map((link) => {
|
||||
return link.name === 'XMLTV' || link.name === 'M3U' ? (
|
||||
<Button
|
||||
href={link.path}
|
||||
target="_blank"
|
||||
color="inherit"
|
||||
startIcon={link.icon}
|
||||
sx={{ px: 1, ml: 0.5 }}
|
||||
key={link.name}
|
||||
>
|
||||
{link.name}
|
||||
</Button>
|
||||
) : (
|
||||
<Tooltip title={link.name}>
|
||||
<IconButton
|
||||
href={link.path}
|
||||
target="_blank"
|
||||
color="inherit"
|
||||
key={link.name}
|
||||
>
|
||||
{link.icon}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
{!smallViewport ? (
|
||||
@@ -362,6 +488,7 @@ export function Root({ children }: { children?: React.ReactNode }) {
|
||||
key={item.name}
|
||||
component={RouterLink}
|
||||
sx={{ display: 'inline-block' }}
|
||||
color="inherit"
|
||||
>
|
||||
{item.icon}
|
||||
</IconButton>
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function NoChannelsCreated() {
|
||||
component={RouterLink}
|
||||
to="/channels/new"
|
||||
>
|
||||
Create your First Channel Now!
|
||||
Create a Channel
|
||||
</Button>
|
||||
</Box>
|
||||
</PaddedPaper>
|
||||
|
||||
@@ -596,6 +596,8 @@ export default function PlexProgrammingSelector() {
|
||||
value={tabValue}
|
||||
onChange={handleChange}
|
||||
aria-label="Plex media selector tabs"
|
||||
variant="scrollable"
|
||||
allowScrollButtonsMobile
|
||||
>
|
||||
<Tab label="Library" {...a11yProps(0)} />
|
||||
{!isUndefined(collectionsData) &&
|
||||
|
||||
@@ -255,7 +255,12 @@ export default function EditChannelPage({ isNew, initialTab }: Props) {
|
||||
</Typography>
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Box sx={{ borderColor: 'primary', borderBottom: 1 }}>
|
||||
<Tabs value={currentTab} onChange={handleChange}>
|
||||
<Tabs
|
||||
value={currentTab}
|
||||
onChange={handleChange}
|
||||
variant="scrollable"
|
||||
allowScrollButtonsMobile
|
||||
>
|
||||
{tabs.map(renderTab)}
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
@@ -40,7 +40,11 @@ export default function SettingsLayout() {
|
||||
</Typography>
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Tabs value={currentTab}>
|
||||
<Tabs
|
||||
value={currentTab}
|
||||
variant="scrollable"
|
||||
allowScrollButtonsMobile
|
||||
>
|
||||
<Tab
|
||||
label="General"
|
||||
value="/settings/general"
|
||||
|
||||
Reference in New Issue
Block a user