Added new breadcrumbs component (#142)

* Added breadcrumbs component

* cr update

* remove comments
This commit is contained in:
Mark D'Avella
2024-03-05 12:07:41 -05:00
committed by GitHub
parent 53235e181f
commit 1eb7091169
16 changed files with 151 additions and 120 deletions

View File

@@ -0,0 +1,45 @@
import {
BreadcrumbsProps,
Link,
Breadcrumbs as MUIBreadcrumbs,
Typography,
} from '@mui/material';
import { isEmpty, map, reject } from 'lodash-es';
import { Link as RouterLink, useLocation } from 'react-router-dom';
import { useGetRouteName } from '../hooks/useRouteName.ts';
export default function Breadcrumbs(props: BreadcrumbsProps) {
const { sx = { mb: 2 }, separator = '', ...restProps } = props;
const location = useLocation();
const pathnames = reject(location.pathname.split('/'), isEmpty);
const getRouteName = useGetRouteName();
return (
<>
<MUIBreadcrumbs
sx={sx}
separator={separator}
aria-label="channel-breadcrumbs"
{...restProps}
>
{map(pathnames, (_, index) => {
const isLast = index === pathnames.length - 1;
const to = `/${pathnames.slice(0, index + 1).join('/')}`;
// Don't link the last item in a breadcrumb because you are on that page
// Don't display crumbs for pages that aren't excplicely defined in useRouteNames hook
return isLast ? (
<Typography color="text.primary" key={to}>
{getRouteName(to) ?? 'null'}
</Typography>
) : getRouteName(to) ? (
<Link component={RouterLink} to={to} key={to}>
{getRouteName(to)}
</Link>
) : null;
})}
</MUIBreadcrumbs>
</>
);
}

View File

@@ -1,4 +1,12 @@
import { Link } from 'react-router-dom';
import {
FreeBreakfast as BreaksIcon,
Expand as FlexIcon,
KeyboardArrowDown as KeyboardArrowDownIcon,
Tv as MediaIcon,
Expand as PaddingIcon,
Directions as RedirectIcon,
Nightlight as RestrictHoursIcon,
} from '@mui/icons-material';
import {
Button,
ButtonGroup,
@@ -9,22 +17,14 @@ import {
alpha,
styled,
} from '@mui/material';
import {
Expand as FlexIcon,
Expand as PaddingIcon,
Directions as RedirectIcon,
FreeBreakfast as BreaksIcon,
KeyboardArrowDown as KeyboardArrowDownIcon,
Nightlight as RestrictHoursIcon,
Tv as MediaIcon,
} from '@mui/icons-material';
import { useState } from 'react';
import AddFlexModal from '../programming_controls/AddFlexModal';
import AddRedirectModal from '../programming_controls/AddRedirectModal';
import AddPaddingModal from '../programming_controls/AddPaddingModal';
import ProgrammingSelectorDialog from './ProgrammingSelectorDialog';
import AddRestrictHoursModal from '../programming_controls/AddRestrictHoursModal';
import { Link } from 'react-router-dom';
import AddBreaksModal from '../programming_controls/AddBreaksModal';
import AddFlexModal from '../programming_controls/AddFlexModal';
import AddPaddingModal from '../programming_controls/AddPaddingModal';
import AddRedirectModal from '../programming_controls/AddRedirectModal';
import AddRestrictHoursModal from '../programming_controls/AddRestrictHoursModal';
import ProgrammingSelectorDialog from './ProgrammingSelectorDialog';
const StyledMenu = styled((props: MenuProps) => (
<Menu

View File

@@ -155,7 +155,6 @@ export function PlexGridItem<T extends PlexMedia>(props: PlexGridItemProps<T>) {
<Collapse in={open} timeout="auto" unmountOnExit>
{renderChildren()}
</Collapse>
<Divider variant="fullWidth" />
</React.Fragment>
);
}

View File

@@ -13,6 +13,7 @@ import {
PlexChildMediaApiType,
PlexMedia,
isPlexCollection,
isPlexMovie,
isPlexSeason,
isPlexShow,
isTerminalItem,
@@ -114,10 +115,18 @@ export function PlexListItem<T extends PlexMedia>(props: PlexListItemProps<T>) {
<ListItemText primary={item.title} secondary={calculateItemRuntime()} />
<Button onClick={(e) => handleItem(e)}>
{hasChildren
? 'Add All'
? `Add ${
isPlexShow(item)
? 'Series'
: isPlexCollection(item)
? 'Collection'
: 'All'
}`
: selectedMediaIds.includes(item.guid)
? 'Remove'
: 'Add'}
: isTerminalItem(item) && isPlexMovie(item)
? 'Add Movie'
: 'Add Episode'}
</Button>
</ListItemButton>
<Collapse in={open} timeout="auto" unmountOnExit>

View File

@@ -1,4 +1,4 @@
import { memoize, find } from 'lodash-es';
import { find, memoize } from 'lodash-es';
type Route = { matcher: RegExp; name: string };
@@ -7,10 +7,54 @@ const namedRoutes: Route[] = [
matcher: /^\/channels$/g,
name: 'Channels',
},
{
matcher: /^\/channels\/new$/g,
name: 'New',
},
{
matcher:
/^\/channels\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/g,
name: 'Channel Edit',
/^\/channels\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}\/watch$/g,
name: 'Watch',
},
{
matcher:
/^\/channels\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}\/edit$/g,
name: 'Edit',
},
{
matcher:
/^\/channels\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}\/programming$/g,
name: 'Programming',
},
{
matcher:
/^\/channels\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}\/programming\/add$/g,
name: 'Add',
},
{
matcher:
/^\/channels\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}\/programming\/time-slot-editor$/g,
name: 'Time Slot Editor',
},
{
matcher: /^\/library$/g,
name: 'Library',
},
{
matcher: /^\/library\/fillers$/g,
name: 'Fillers',
},
{
matcher: /^\/library\/fillers\/new$/g,
name: 'New',
},
{
matcher: /^\/library\/custom-shows$/g,
name: 'Custom Shows',
},
{
matcher: /^\/library\/custom-shows\/new$/g,
name: 'New',
},
];

View File

@@ -1,9 +1,7 @@
import {
Box,
Breadcrumbs,
Button,
CircularProgress,
Link,
Snackbar,
Typography,
} from '@mui/material';
@@ -14,6 +12,7 @@ import { ZodiosError } from '@zodios/core';
import { chain, findIndex, first, isUndefined, map } from 'lodash-es';
import { useState } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import Breadcrumbs from '../../components/Breadcrumbs.tsx';
import { ChannelProgrammingConfig } from '../../components/channel_config/ChannelProgrammingConfig.tsx';
import { apiClient } from '../../external/api.ts';
import { channelProgramUniqueId } from '../../helpers/util.ts';
@@ -123,17 +122,7 @@ export default function ChannelProgrammingPage() {
message={snackStatus.message}
sx={{ backgroundColor: snackStatus.color }}
/>
<Breadcrumbs sx={{ mb: 2 }} separator="" aria-label="channel-breadcrumb">
<Link
underline="hover"
color="inherit"
component={RouterLink}
to="/channels"
>
Channels
</Link>
<Box>Manage Programming</Box>
</Breadcrumbs>
<Breadcrumbs />
<Typography variant="h4" sx={{ mb: 2 }}>
Channel {channel.number} Programming
</Typography>

View File

@@ -1,7 +1,5 @@
import { Badge } from '@mui/material';
import Box from '@mui/material/Box';
import Breadcrumbs from '@mui/material/Breadcrumbs';
import Link from '@mui/material/Link';
import Paper from '@mui/material/Paper';
import Tab from '@mui/material/Tab';
import Tabs from '@mui/material/Tabs';
@@ -10,7 +8,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { SaveChannelRequest } from '@tunarr/types';
import { usePrevious } from '@uidotdev/usehooks';
import { ZodiosError } from '@zodios/core';
import { isUndefined, keys, some } from 'lodash-es';
import { keys, some } from 'lodash-es';
import { useEffect, useState } from 'react';
import {
FieldPath,
@@ -19,7 +17,8 @@ import {
SubmitHandler,
useForm,
} from 'react-hook-form';
import { Link as RouterLink, useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import Breadcrumbs from '../../components/Breadcrumbs.tsx';
import ChannelEpgConfig from '../../components/channel_config/ChannelEpgConfig.tsx';
import { ChannelFlexConfig } from '../../components/channel_config/ChannelFlexConfig.tsx';
import ChannelPropertiesEditor from '../../components/channel_config/ChannelPropertiesEditor.tsx';
@@ -227,17 +226,7 @@ export default function EditChannelPage({ isNew }: Props) {
<ChannelEditContext.Provider
value={{ channelEditorState, setChannelEditorState }}
>
<Breadcrumbs sx={{ mb: 2 }} separator="" aria-label="channel-breadcrumb">
<Link
underline="hover"
color="inherit"
component={RouterLink}
to="/channels"
>
Channels
</Link>
<Box>Edit Channel</Box>
</Breadcrumbs>
<Breadcrumbs />
{workingChannel && (
<div>
<Typography variant="h4" sx={{ mb: 2 }}>

View File

@@ -1,6 +1,6 @@
import { Breadcrumbs, Link, Typography } from '@mui/material';
import { isEmpty, map, reject } from 'lodash-es';
import { Link as RouterLink, useLocation } from 'react-router-dom';
import { isEmpty, reject } from 'lodash-es';
import { useLocation } from 'react-router-dom';
import Breadcrumbs from '../../components/Breadcrumbs.tsx';
import PaddedPaper from '../../components/base/PaddedPaper.tsx';
import ProgrammingSelector from '../../components/channel_config/ProgrammingSelector.tsx';
import { useGetRouteName } from '../../hooks/useRouteName.ts';
@@ -9,23 +9,10 @@ export default function ProgrammingSelectorPage() {
const location = useLocation();
const pathnames = reject(location.pathname.split('/'), isEmpty);
const getRouteName = useGetRouteName();
return (
<>
<Breadcrumbs sx={{ mb: 2 }} separator="" aria-label="channel-breadcrumb">
{map(pathnames, (_, index) => {
const isLast = index === pathnames.length - 1;
const to = `/${pathnames.slice(0, index + 1).join('/')}`;
return isLast ? (
<Typography color="text.primary" key={to}>
{getRouteName(to) ?? 'null'}
</Typography>
) : (
<Link component={RouterLink} to={to} key={to}>
{getRouteName(to) ?? 'null'}
</Link>
);
})}
</Breadcrumbs>
<Breadcrumbs />
<PaddedPaper>
<ProgrammingSelector />
</PaddedPaper>

View File

@@ -3,7 +3,6 @@ import AddIcon from '@mui/icons-material/Add';
import {
Alert,
Box,
Breadcrumbs,
Button,
Divider,
FormControl,
@@ -12,7 +11,6 @@ import {
Grid,
IconButton,
InputLabel,
Link,
MenuItem,
Select,
SelectChangeEvent,
@@ -55,6 +53,7 @@ import {
useWatch,
} from 'react-hook-form';
import { Link as RouterLink } from 'react-router-dom';
import Breadcrumbs from '../../components/Breadcrumbs.tsx';
import PaddedPaper from '../../components/base/PaddedPaper.tsx';
import ChannelProgrammingList from '../../components/channel_config/ChannelProgrammingList.tsx';
import { apiClient } from '../../external/api.ts';
@@ -600,17 +599,7 @@ export default function TimeSlotEditorPage() {
: null}
</Alert>
</Snackbar>
<Breadcrumbs sx={{ mb: 2 }}>
<Link
underline="hover"
color="inherit"
component={RouterLink}
to=".."
relative="path"
>
Back
</Link>
</Breadcrumbs>
<Breadcrumbs />
<Typography variant="h4" sx={{ mb: 2 }}>
Edit Time Slots (Channel {channel?.number})
</Typography>

View File

@@ -1,6 +1,7 @@
import AddCircleIcon from '@mui/icons-material/AddCircle';
import DeleteIcon from '@mui/icons-material/Delete';
import EditIcon from '@mui/icons-material/Edit';
import { IconButton, Tooltip } from '@mui/material';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Paper from '@mui/material/Paper';
@@ -12,9 +13,9 @@ import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Typography from '@mui/material/Typography';
import { Link } from 'react-router-dom';
import Breadcrumbs from '../../components/Breadcrumbs.tsx';
import { usePreloadedData } from '../../hooks/preloadedDataHook.ts';
import { customShowsLoader } from '../../preloaders/customShowLoaders.ts';
import { IconButton, Tooltip } from '@mui/material';
export default function CustomShowsPage() {
const customShows = usePreloadedData(customShowsLoader);
@@ -57,6 +58,7 @@ export default function CustomShowsPage() {
return (
<Box>
<Breadcrumbs />
<Box display="flex" mb={2}>
<Typography flexGrow={1} variant="h4">
Custom Shows

View File

@@ -15,19 +15,20 @@ import AccordionDetails from '@mui/material/AccordionDetails';
import AccordionSummary from '@mui/material/AccordionSummary';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { CustomShow } from '@tunarr/types';
import { useCallback } from 'react';
import { useLoaderData, useNavigate } from 'react-router-dom';
import Breadcrumbs from '../../components/Breadcrumbs.tsx';
import PaddedPaper from '../../components/base/PaddedPaper.tsx';
import AddSelectedMediaButton from '../../components/channel_config/AddSelectedMediaButton.tsx';
import ProgrammingSelector from '../../components/channel_config/ProgrammingSelector.tsx';
import { apiClient } from '../../external/api.ts';
import {
addPlexMediaToCurrentCustomShow,
removeCustomShowProgram,
} from '../../store/channelEditor/actions.ts';
import useStore from '../../store/index.ts';
import { CustomShow } from '@tunarr/types';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { apiClient } from '../../external/api.ts';
type Props = { isNew: boolean };
@@ -122,6 +123,7 @@ export default function EditCustomShowPage({ isNew }: Props) {
return (
<div>
<Box>
<Breadcrumbs />
<Typography variant="h4" sx={{ mb: 2 }}>
New Custom Show
</Typography>

View File

@@ -1,11 +1,9 @@
import DeleteIcon from '@mui/icons-material/Delete';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import {
Breadcrumbs,
Button,
Divider,
IconButton,
Link,
List,
ListItem,
ListItemText,
@@ -20,7 +18,8 @@ import Typography from '@mui/material/Typography';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useCallback, useEffect } from 'react';
import { Controller, SubmitHandler, useForm } from 'react-hook-form';
import { Link as RouterLink, useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import Breadcrumbs from '../../components/Breadcrumbs.tsx';
import PaddedPaper from '../../components/base/PaddedPaper.tsx';
import AddSelectedMediaButton from '../../components/channel_config/AddSelectedMediaButton.tsx';
import ProgrammingSelector from '../../components/channel_config/ProgrammingSelector.tsx';
@@ -166,17 +165,7 @@ export default function EditFillerPage({ isNew }: Props) {
return (
<Box>
<Breadcrumbs sx={{ mb: 2 }} separator="" aria-label="filler-breadcrumb">
<Link
underline="hover"
color="inherit"
component={RouterLink}
to="/library/fillers"
>
Filler Lists
</Link>
<Box>{isNew ? 'New' : 'Edit'} Filler List</Box>
</Breadcrumbs>
<Breadcrumbs />
<Box component="form" onSubmit={handleSubmit(saveFiller)}>
<Box>
<Typography variant="h4" sx={{ mb: 2 }}>

View File

@@ -14,6 +14,7 @@ import TableRow from '@mui/material/TableRow';
import Typography from '@mui/material/Typography';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { Link } from 'react-router-dom';
import Breadcrumbs from '../../components/Breadcrumbs.tsx';
import { apiClient } from '../../external/api.ts';
import { useFillerLists } from '../../hooks/useFillerLists.ts';
@@ -81,6 +82,7 @@ export default function FillerListsPage() {
return (
<Box>
<Breadcrumbs />
<Box display="flex" mb={2}>
<Typography flexGrow={1} variant="h4">
Filler Lists

View File

@@ -1,6 +1,7 @@
import AddCircleIcon from '@mui/icons-material/AddCircle';
import DeleteIcon from '@mui/icons-material/Delete';
import EditIcon from '@mui/icons-material/Edit';
import { IconButton, Tooltip } from '@mui/material';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Paper from '@mui/material/Paper';
@@ -12,9 +13,9 @@ import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Typography from '@mui/material/Typography';
import { Link } from 'react-router-dom';
import Breadcrumbs from '../../components/Breadcrumbs.tsx';
import { usePreloadedData } from '../../hooks/preloadedDataHook.ts';
import { customShowsLoader } from '../../preloaders/customShowLoaders.ts';
import { IconButton, Tooltip } from '@mui/material';
export default function FillersPage() {
const fillers = usePreloadedData(customShowsLoader);
@@ -57,6 +58,7 @@ export default function FillersPage() {
return (
<Box>
<Breadcrumbs />
<Box display="flex" mb={2}>
<Typography flexGrow={1} variant="h4">
Filler Lists

View File

@@ -1,3 +1,4 @@
import { ArrowForward } from '@mui/icons-material';
import {
Button,
Card,
@@ -7,7 +8,6 @@ import {
Typography,
} from '@mui/material';
import { Link } from 'react-router-dom';
import { ArrowForward } from '@mui/icons-material';
export default function LibraryIndexPage() {
return (
@@ -38,7 +38,7 @@ export default function LibraryIndexPage() {
size="small"
variant="contained"
component={Link}
to="/library/filler"
to="/library/fillers"
endIcon={<ArrowForward />}
>
Edit Fillers

View File

@@ -1,8 +1,5 @@
import Box from '@mui/material/Box';
import Breadcrumbs from '@mui/material/Breadcrumbs';
import Link from '@mui/material/Link';
import Typography from '@mui/material/Typography';
import { Link as RouterLink } from 'react-router-dom';
import Breadcrumbs from '../../components/Breadcrumbs.tsx';
import Video from '../../components/Video.tsx';
import { usePreloadedChannel } from '../../hooks/usePreloadedChannel.ts';
@@ -12,21 +9,7 @@ export default function ChannelWatchPage() {
return (
channel && (
<div>
<Breadcrumbs
sx={{ mb: 2 }}
separator=""
aria-label="channel-breadcrumb"
>
<Link
underline="hover"
color="inherit"
component={RouterLink}
to="/channels"
>
Channels
</Link>
<Box>Manage Programming</Box>
</Breadcrumbs>
<Breadcrumbs />
<Typography variant="h4" sx={{ mb: 2 }}>
Channel {channel.number} Live
</Typography>