Skip to content

Commit

Permalink
Custom Show Programming Picker - next steps
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbenincasa committed Mar 13, 2024
1 parent 54bf8bb commit 04815c1
Show file tree
Hide file tree
Showing 15 changed files with 592 additions and 174 deletions.
145 changes: 144 additions & 1 deletion web/src/components/channel_config/CustomShowProgrammingSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,146 @@
import {
Box,
Button,
Divider,
LinearProgress,
List,
ListItem,
ListItemText,
} from '@mui/material';
import { ContentProgram, isContentProgram } from '@tunarr/types';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import { chain, isEmpty, isNil, property } from 'lodash-es';
import { MouseEvent, useCallback, useState } from 'react';
import { useIntersectionObserver } from 'usehooks-ts';
import { forProgramType } from '../../helpers/util';
import { useCustomShow } from '../../hooks/useCustomShows';
import useStore from '../../store';
import { addSelectedMedia } from '../../store/programmingSelector/actions';

dayjs.extend(duration);

export function CustomShowProgrammingSelector() {
return <div></div>;
const selectedCustomShow = useStore((s) =>
s.currentLibrary?.type === 'custom-show' ? s.currentLibrary : null,
);
const viewType = useStore((state) => state.theme.programmingSelectorView);
const [scrollParams, setScrollParams] = useState({ limit: 0, max: -1 });

const [showResult, programsResult] = useCustomShow(
/*id=*/ selectedCustomShow?.library.id ?? '',
/*enabled=*/ !isNil(selectedCustomShow),
/*includePrograms=*/ true,
);

const isLoading = showResult.isLoading || programsResult.isLoading;

const formattedTitle = useCallback(
forProgramType({
content: (p) => p.title,
}),
[],
);

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

const { ref } = useIntersectionObserver({
onChange: (_, entry) => {
if (entry.isIntersecting && scrollParams.limit < scrollParams.max) {
setScrollParams(({ limit: prevLimit, max }) => ({
max,
limit: prevLimit + 10,
}));
}
},
threshold: 0.5,
});

const handleItem = useCallback(
(e: MouseEvent<HTMLButtonElement>, item: ContentProgram) => {
e.stopPropagation();
if (selectedCustomShow) {
addSelectedMedia({
type: 'custom-show',
customShowId: selectedCustomShow.library.id,
program: item,
});
}
},
[],
);

const renderListItems = () => {
if (
showResult.data &&
programsResult.data &&
programsResult.data.length > 0
) {
return chain(programsResult.data)
.filter(isContentProgram)
.filter(property('persisted'))
.map((program) => {
let title = formattedTitle(program);
let epTitle = formattedEpisodeTitle(program);
if (!isEmpty(epTitle)) {
title += ` - ${epTitle}`;
}

return (
<ListItem dense key={program.id}>
<ListItemText
// TODO add season and episode number?
primary={title}
secondary={dayjs.duration(program.duration).humanize()}
/>
<Button
onClick={(e) => handleItem(e, program)}
variant="contained"
>
Add
</Button>
</ListItem>
);
})
.compact()
.value();
}

return null;
};

return (
<Box>
<LinearProgress
sx={{
visibility: isLoading ? 'visible' : 'hidden',
height: 10,
marginTop: 1,
}}
/>
<List
component="nav"
sx={{
mt: 2,
width: '100%',
maxHeight: 1200,
overflowY: 'scroll',
display: viewType === 'grid' ? 'flex' : 'block',
flexWrap: 'wrap',
gap: '10px',
justifyContent: 'space-between',
}}
>
{renderListItems()}
<div style={{ height: 40 }} ref={ref}></div>
</List>

<Divider sx={{ mt: 3, mb: 2 }} />
</Box>
);
}
4 changes: 2 additions & 2 deletions web/src/components/channel_config/PlexDirectoryListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { usePlexTyped2 } from '../../hooks/plexHooks.ts';
import useStore from '../../store/index.ts';
import {
addKnownMediaForServer,
addSelectedMedia,
addPlexSelectedMedia,
} from '../../store/programmingSelector/actions.ts';
import { PlexListItem } from './PlexListItem.tsx';

Expand Down Expand Up @@ -102,7 +102,7 @@ export function PlexDirectoryListItem(props: {
const addItems = useCallback(
(e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
addSelectedMedia(server.name, [item]);
addPlexSelectedMedia(server.name, [item]);
},
[item, server.name],
);
Expand Down
8 changes: 4 additions & 4 deletions web/src/components/channel_config/PlexGridItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import { usePlexTyped } from '../../hooks/plexHooks.ts';
import useStore from '../../store/index.ts';
import {
addKnownMediaForServer,
addSelectedMedia,
removeSelectedMedia,
addPlexSelectedMedia,
removePlexSelectedMedia,
} from '../../store/programmingSelector/actions.ts';

export interface PlexGridItemProps<T extends PlexMedia> {
Expand Down Expand Up @@ -69,9 +69,9 @@ export function PlexGridItem<T extends PlexMedia>(props: PlexGridItemProps<T>) {
e.stopPropagation();

if (selectedMediaIds.includes(item.guid)) {
removeSelectedMedia(selectedServer!.name, [item.guid]);
removePlexSelectedMedia(selectedServer!.name, [item.guid]);
} else {
addSelectedMedia(selectedServer!.name, [item]);
addPlexSelectedMedia(selectedServer!.name, [item]);
}
},
[item, selectedServer, selectedMediaIds],
Expand Down
14 changes: 9 additions & 5 deletions web/src/components/channel_config/PlexListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@ import {
isPlexShow,
isTerminalItem,
} from '@tunarr/types/plex';
import { filter } from 'lodash-es';
import React, { MouseEvent, useCallback, useEffect, useState } from 'react';
import { prettyItemDuration } from '../../helpers/util.ts';
import { usePlexTyped } from '../../hooks/plexHooks.ts';
import useStore from '../../store/index.ts';
import {
addKnownMediaForServer,
addSelectedMedia,
removeSelectedMedia,
addPlexSelectedMedia,
removePlexSelectedMedia,
} from '../../store/programmingSelector/actions.ts';
import { PlexSelectedMedia } from '../../store/programmingSelector/store.ts';

export interface PlexListItemProps<T extends PlexMedia> {
item: T;
Expand All @@ -48,7 +50,9 @@ export function PlexListItem<T extends PlexMedia>(props: PlexListItemProps<T>) {
hasChildren && open,
);
const selectedServer = useStore((s) => s.currentServer);
const selectedMedia = useStore((s) => s.selectedMedia);
const selectedMedia = useStore((s) =>
filter(s.selectedMedia, (m): m is PlexSelectedMedia => m.type === 'plex'),
);
const selectedMediaIds = selectedMedia.map((item) => item['guid']);

const handleClick = () => {
Expand All @@ -66,9 +70,9 @@ export function PlexListItem<T extends PlexMedia>(props: PlexListItemProps<T>) {
e.stopPropagation();

if (selectedMediaIds.includes(item.guid)) {
removeSelectedMedia(selectedServer!.name, [item.guid]);
removePlexSelectedMedia(selectedServer!.name, [item.guid]);
} else {
addSelectedMedia(selectedServer!.name, [item]);
addPlexSelectedMedia(selectedServer!.name, [item]);
}
},
[item, selectedServer, selectedMediaIds],
Expand Down
11 changes: 3 additions & 8 deletions web/src/components/channel_config/PlexProgrammingSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
TextField,
ToggleButton,
ToggleButtonGroup,
Typography,
} from '@mui/material';
import { DataTag, useInfiniteQuery, useQuery } from '@tanstack/react-query';
import {
Expand All @@ -43,12 +42,10 @@ import useDebouncedState from '../../hooks/useDebouncedState';
import useStore from '../../store';
import { addKnownMediaForServer } from '../../store/programmingSelector/actions';
import { setProgrammingSelectorViewState } from '../../store/themeEditor/actions';
import { ProgramSelectorViewType } from '../../types';
import ConnectPlex from '../settings/ConnectPlex';
import { PlexGridItem } from './PlexGridItem';
import { PlexListItem } from './PlexListItem';
import SelectedProgrammingList from './SelectedProgrammingList';

type ViewType = 'list' | 'grid';

type Props = {
onAddSelectedMedia: (items: EnrichedPlexMedia[]) => void;
Expand Down Expand Up @@ -78,13 +75,13 @@ export default function PlexProgrammingSelector({ onAddSelectedMedia }: Props) {
setSearchBarOpen(false);
}, [setSearch]);

const setViewType = (view: ViewType) => {
const setViewType = (view: ProgramSelectorViewType) => {
setProgrammingSelectorViewState(view);
};

const handleFormat = (
_event: React.MouseEvent<HTMLElement>,
newFormats: ViewType,
newFormats: ProgramSelectorViewType,
) => {
setViewType(newFormats);
};
Expand Down Expand Up @@ -341,8 +338,6 @@ export default function PlexProgrammingSelector({ onAddSelectedMedia }: Props) {
</List>

<Divider sx={{ mt: 3, mb: 2 }} />
<Typography>Selected Items</Typography>
<SelectedProgrammingList onAddSelectedMedia={onAddSelectedMedia} />
</>
)}
</>
Expand Down
29 changes: 17 additions & 12 deletions web/src/components/channel_config/ProgrammingSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
MenuItem,
Select,
Stack,
Typography,
} from '@mui/material';
import { PlexMedia, isPlexDirectory } from '@tunarr/types/plex';
import { find, isEmpty, isNil, isUndefined } from 'lodash-es';
Expand All @@ -19,6 +20,7 @@ import {
} from '../../store/programmingSelector/actions.ts';
import { CustomShowProgrammingSelector } from './CustomShowProgrammingSelector.tsx';
import PlexProgrammingSelector from './PlexProgrammingSelector.tsx';
import SelectedProgrammingList from './SelectedProgrammingList.tsx';

export interface PlexListItemProps<T extends PlexMedia> {
item: T;
Expand Down Expand Up @@ -89,11 +91,7 @@ export default function ProgrammingSelector({ onAddSelectedMedia }: Props) {
});

useEffect(() => {
if (
mediaSource === 'custom-shows' &&
customShows &&
customShows.length > 0
) {
if (mediaSource === 'custom-shows' && customShows.length > 0) {
setProgrammingListLibrary({
type: 'custom-show',
library: customShows[0],
Expand All @@ -102,13 +100,13 @@ export default function ProgrammingSelector({ onAddSelectedMedia }: Props) {
}, [mediaSource, customShows]);

const onMediaSourceChange = useCallback(
(mediaSource: string) => {
if (mediaSource === 'custom-shows') {
(newMediaSource: string) => {
if (newMediaSource === 'custom-shows') {
// Not dealing with a server
setProgrammingListingServer(undefined);
setMediaSource(mediaSource);
setMediaSource(newMediaSource);
} else {
const server = find(plexServers, { name: mediaSource });
const server = find(plexServers, { name: newMediaSource });
if (server) {
setProgrammingListingServer(server);
setMediaSource(server.name);
Expand All @@ -120,16 +118,21 @@ export default function ProgrammingSelector({ onAddSelectedMedia }: Props) {

const onLibraryChange = useCallback(
(libraryUuid: string) => {
// TODO support loading custom shows
if (selectedServer) {
if (mediaSource === 'custom-shows') {
console.log('hello', customShows);
const library = find(customShows, { id: libraryUuid });
if (library) {
setProgrammingListLibrary({ type: 'custom-show', library });
}
} else if (selectedServer) {
const known = knownMedia[selectedServer.name] ?? {};
const library = known[libraryUuid];
if (library && isPlexDirectory(library)) {
setProgrammingListLibrary({ type: 'plex', library });
}
}
},
[knownMedia, selectedServer],
[mediaSource, knownMedia, selectedServer],
);

const renderMediaSourcePrograms = () => {
Expand Down Expand Up @@ -212,6 +215,8 @@ export default function ProgrammingSelector({ onAddSelectedMedia }: Props) {
)}
</Stack>
{renderMediaSourcePrograms()}
<Typography>Selected Items</Typography>
<SelectedProgrammingList onAddSelectedMedia={onAddSelectedMedia} />
</>
);
}
Loading

0 comments on commit 04815c1

Please sign in to comment.