Skip to content

Commit

Permalink
Rework modal (#439)
Browse files Browse the repository at this point in the history
* light modal refresh v1

* adjust how times are displayed

* fix type issues for deployment
  • Loading branch information
markdavella authored May 20, 2024
1 parent 06c456a commit c4ef9fc
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 33 deletions.
108 changes: 84 additions & 24 deletions web/src/components/ProgramDetailsDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { OpenInNew } from '@mui/icons-material';
import { Close as CloseIcon, OpenInNew } from '@mui/icons-material';
import {
Box,
Button,
Chip,
Dialog,
DialogContent,
Expand All @@ -9,10 +10,13 @@ import {
Skeleton,
Stack,
Typography,
useMediaQuery,
useTheme,
} from '@mui/material';
import { createExternalId } from '@tunarr/shared';
import { forProgramType } from '@tunarr/shared/util';
import { ChannelProgram } from '@tunarr/types';
import { ChannelProgram, TvGuideProgram } from '@tunarr/types';
import dayjs, { Dayjs } from 'dayjs';
import { isUndefined } from 'lodash-es';
import {
ReactEventHandler,
Expand All @@ -28,7 +32,9 @@ import { useSettings } from '../store/settings/selectors';
type Props = {
open: boolean;
onClose: () => void;
program: ChannelProgram | undefined;
program: TvGuideProgram | ChannelProgram | undefined;
start?: Dayjs;
stop?: Dayjs;
};

const formattedTitle = forProgramType({
Expand All @@ -43,12 +49,16 @@ type ThumbLoadState = 'loading' | 'error' | 'success';
export default function ProgramDetailsDialog({
open,
onClose,
start,
stop,
program,
}: Props) {
const settings = useSettings();
const [thumbLoadState, setThumbLoadState] =
useState<ThumbLoadState>('loading');
const imageRef = useRef<HTMLImageElement>(null);
const theme = useTheme();
const smallViewport = useMediaQuery(theme.breakpoints.down('sm'));

const rating = useMemo(
() =>
Expand All @@ -69,12 +79,22 @@ export default function ProgramDetailsDialog({
[],
);

const episodeTitle = useMemo(
() =>
forProgramType({
custom: (p) => p.program?.episodeTitle ?? '',
content: (p) => p.episodeTitle,
default: '',
}),
[],
);

const durationChip = useMemo(
() =>
forProgramType({
content: (program) => (
<Chip
color="secondary"
color="primary"
label={prettyItemDuration(program.duration)}
sx={{ mt: 1 }}
/>
Expand All @@ -87,7 +107,7 @@ export default function ProgramDetailsDialog({
(program: ChannelProgram) => {
const ratingString = rating(program);
return ratingString ? (
<Chip color="secondary" label={ratingString} sx={{ mx: 1, mt: 1 }} />
<Chip color="primary" label={ratingString} sx={{ mx: 1, mt: 1 }} />
) : null;
},
[rating],
Expand Down Expand Up @@ -139,6 +159,7 @@ export default function ProgramDetailsDialog({
const thumbUrl = program ? thumbnailImage(program) : null;
const externalUrl = program ? externalLink(program) : null;
const programSummary = program ? summary(program) : null;
const programEpisodeTitle = program ? episodeTitle(program) : null;

useEffect(() => {
setThumbLoadState('loading');
Expand All @@ -153,55 +174,82 @@ export default function ProgramDetailsDialog({
setThumbLoadState('error');
}, []);

const isEpisode =
program && program.type === 'content' && program.subtype === 'episode';
const imageWidth = smallViewport ? (isEpisode ? '100%' : '55%') : 240;
const programStart = dayjs(start);
const programEnd = dayjs(stop);

return (
program && (
<Dialog open={open && !isUndefined(program)} onClose={onClose}>
<DialogTitle variant="h4">
<Dialog
open={open && !isUndefined(program)}
onClose={onClose}
fullScreen={smallViewport}
>
<DialogTitle variant="h4" sx={{ marginRight: 3 }}>
{formattedTitle(program)}{' '}
{externalUrl && (
<IconButton
component="a"
target="_blank"
href={externalUrl}
size="small"
>
<OpenInNew />
</IconButton>
)}
<IconButton
edge="start"
color="inherit"
onClick={() => onClose()}
aria-label="close"
sx={{ position: 'absolute', top: 10, right: 10 }}
size="large"
>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent>
<Stack spacing={2}>
<Box>
{durationChip(program)}
{ratingChip(program)}
<Chip
label={`${programStart.format('h:mm')} - ${programEnd.format(
'h:mma',
)}`}
sx={{ mt: 1 }}
color="primary"
/>
</Box>
<Stack direction="row" spacing={2}>
<Box>
<Stack
direction="row"
spacing={smallViewport ? 0 : 2}
flexDirection={smallViewport ? 'column' : 'row'}
>
<Box sx={{ textAlign: 'center' }}>
<Box
component="img"
width={240}
width={imageWidth}
src={thumbUrl ?? ''}
alt={formattedTitle(program)}
onLoad={onLoad}
ref={imageRef}
sx={{
display: thumbLoadState !== 'success' ? 'none' : undefined,
borderRadius: '10px',
}}
onError={onError}
/>
{(thumbLoadState === 'loading' ||
thumbLoadState === 'error') && (
<Skeleton
variant="rectangular"
width={240}
height={360}
width={smallViewport ? '100%' : imageWidth}
height={500}
animation={thumbLoadState === 'loading' ? 'pulse' : false}
></Skeleton>
)}
</Box>
<Box>
{programEpisodeTitle ? (
<Typography variant="h5" sx={{ mb: 1 }}>
{programEpisodeTitle}
</Typography>
) : null}
{programSummary ? (
<Typography id="modal-modal-description" sx={{ mt: 1 }}>
<Typography id="modal-modal-description" sx={{ mb: 1 }}>
{programSummary}
</Typography>
) : (
Expand All @@ -212,9 +260,21 @@ export default function ProgramDetailsDialog({
backgroundColor: (theme) =>
theme.palette.background.default,
}}
width={240}
width={imageWidth}
/>
)}
{externalUrl && (
<Button
component="a"
target="_blank"
href={externalUrl}
size="small"
endIcon={<OpenInNew />}
variant="contained"
>
View in Plex
</Button>
)}
</Box>
</Stack>
</Stack>
Expand Down
24 changes: 19 additions & 5 deletions web/src/components/channel_config/ChannelProgrammingList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import { forProgramType } from '@tunarr/shared/util';
import { ChannelProgram } from '@tunarr/types';
import dayjs from 'dayjs';
import dayjs, { Dayjs } from 'dayjs';
import { findIndex, isUndefined, join, map, negate, reject } from 'lodash-es';
import { CSSProperties, useCallback, useState } from 'react';
import { useDrag, useDrop } from 'react-dnd';
Expand Down Expand Up @@ -309,6 +309,11 @@ const ProgramListItem = ({
);
};

type GuideTime = {
start?: Dayjs;
stop?: Dayjs;
};

export default function ChannelProgrammingList({
programList: passedProgramList,
programListSelector = defaultProps.programListSelector,
Expand All @@ -322,6 +327,7 @@ export default function ChannelProgrammingList({
const [focusedProgramDetails, setFocusedProgramDetails] = useState<
ChannelProgram | undefined
>();
const [startStop, setStartStop] = useState<GuideTime>({});
const [editProgram, setEditProgram] = useState<
((UIFlexProgram | UIRedirectProgram) & { index: number }) | undefined
>();
Expand Down Expand Up @@ -349,9 +355,15 @@ export default function ChannelProgrammingList({
[passedProgramList],
);

const openDetailsDialog = useCallback((program: ChannelProgram) => {
setFocusedProgramDetails(program);
}, []);
const openDetailsDialog = useCallback(
(program: ChannelProgram, startTimeDate: Date) => {
setFocusedProgramDetails(program);
const start = dayjs(startTimeDate);
const stop = start.add(program.duration);
setStartStop({ start, stop });
},
[],
);

const openEditDialog = useCallback(
(program: (UIFlexProgram | UIRedirectProgram) & { index: number }) => {
Expand All @@ -376,7 +388,7 @@ export default function ChannelProgrammingList({
moveProgram={moveProgram}
findProgram={findProgram}
enableDrag={!!enableDnd}
onInfoClicked={openDetailsDialog}
onInfoClicked={() => openDetailsDialog(program, startTimeDate)}
onEditClicked={openEditDialog}
/>
);
Expand Down Expand Up @@ -449,6 +461,8 @@ export default function ChannelProgrammingList({
open={!isUndefined(focusedProgramDetails)}
onClose={() => setFocusedProgramDetails(undefined)}
program={focusedProgramDetails}
start={startStop.start}
stop={startStop.stop}
/>
<AddFlexModal
open={!isUndefined(editProgram) && editProgram.type === 'flex'}
Expand Down
18 changes: 14 additions & 4 deletions web/src/components/guide/TvGuide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -368,10 +368,18 @@ export function TvGuide({ channelId, start, end }: Props) {
<Box sx={{ fontSize: '13px', fontStyle: 'italic' }}>
{episodeTitle}
</Box>
<Box sx={{ fontSize: '12px' }}>
{`${programStart.format('h:mm')} - ${programEnd.format('h:mma')}`}
{isPlaying ? ` (${remainingTime}m remaining)` : null}
</Box>
{((smallViewport && pct > 20) || (!smallViewport && pct > 8)) && (
<>
<Box sx={{ fontSize: '12px' }}>
{`${programStart.format('h:mm')} - ${programEnd.format(
'h:mma',
)}`}
</Box>
<Box sx={{ fontSize: '12px' }}>
{isPlaying ? ` (${remainingTime}m left)` : null}
</Box>
</>
)}
</GuideItem>
{endOfAvailableProgramming
? renderUnavailableProgramming(finalBlockWidth, index)
Expand Down Expand Up @@ -459,6 +467,8 @@ export function TvGuide({ channelId, start, end }: Props) {
open={!isUndefined(modalProgram)}
onClose={() => handleModalClose()}
program={modalProgram}
start={dayjs(modalProgram?.start)}
stop={dayjs(modalProgram?.stop)}
/>
<Box display="flex" ref={ref}>
<Box
Expand Down

0 comments on commit c4ef9fc

Please sign in to comment.