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 7722091 commit ae1e1e0
Show file tree
Hide file tree
Showing 16 changed files with 607 additions and 188 deletions.
8 changes: 6 additions & 2 deletions web/src/components/channel_config/AddSelectedMediaButton.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Tooltip } from '@mui/material';
import Button, { ButtonProps } from '@mui/material/Button';
import { flattenDeep } from 'lodash-es';
import { filter, flattenDeep } from 'lodash-es';
import { sequentialPromises } from '../../helpers/util.ts';
import { EnrichedPlexMedia, enumeratePlexItem } from '../../hooks/plexHooks.ts';
import useStore from '../../store/index.ts';
import { clearSelectedMedia } from '../../store/programmingSelector/actions.ts';
import { PlexSelectedMedia } from '../../store/programmingSelector/store.ts';

type Props = {
onAdd: (items: EnrichedPlexMedia[]) => void;
Expand All @@ -17,7 +18,10 @@ export default function AddSelectedMediaButton({
...rest
}: Props) {
const knownMedia = useStore((s) => s.knownMediaByServer);
const selectedMedia = useStore((s) => s.selectedMedia);
// TODO support custom shows
const selectedMedia = useStore((s) =>
filter(s.selectedMedia, (m): m is PlexSelectedMedia => m.type === 'plex'),
);

const addSelectedItems = () => {
sequentialPromises(selectedMedia, (selected) => {
Expand Down
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
16 changes: 10 additions & 6 deletions web/src/components/channel_config/PlexGridItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@ import {
isPlexCollection,
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 PlexGridItemProps<T extends PlexMedia> {
item: T;
Expand All @@ -51,8 +53,10 @@ export function PlexGridItem<T extends PlexMedia>(props: PlexGridItemProps<T>) {
hasChildren && open,
);
const selectedServer = useStore((s) => s.currentServer);
const selectedMedia = useStore((s) => s.selectedMedia);
const selectedMediaIds = selectedMedia.map((item) => item['guid']);
const selectedMedia = useStore((s) =>
filter(s.selectedMedia, (p): p is PlexSelectedMedia => p.type === 'plex'),
);
const selectedMediaIds = selectedMedia.map((item) => item.guid);

const handleClick = () => {
setOpen(!open);
Expand All @@ -69,9 +73,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
20 changes: 5 additions & 15 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 @@ -34,27 +33,20 @@ import { Fragment, useCallback, useEffect, useState } from 'react';
import { useIntersectionObserver } from 'usehooks-ts';
import { toggle } from '../../helpers/util';
import {
EnrichedPlexMedia,
fetchPlexPath,
usePlex,
usePlex
} from '../../hooks/plexHooks';
import { usePlexServerSettings } from '../../hooks/settingsHooks';
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;
};

export default function PlexProgrammingSelector({ onAddSelectedMedia }: Props) {
export default function PlexProgrammingSelector() {
const { data: plexServers } = usePlexServerSettings();
const selectedServer = useStore((s) => s.currentServer);
const selectedLibrary = useStore((s) =>
Expand All @@ -78,13 +70,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 +333,6 @@ export default function PlexProgrammingSelector({ onAddSelectedMedia }: Props) {
</List>

<Divider sx={{ mt: 3, mb: 2 }} />
<Typography>Selected Items</Typography>
<SelectedProgrammingList onAddSelectedMedia={onAddSelectedMedia} />
</>
)}
</>
Expand Down
Loading

0 comments on commit ae1e1e0

Please sign in to comment.