mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Refactoring Section Container
This commit is contained in:
parent
c3e253d98d
commit
12995545b9
11 changed files with 257 additions and 183 deletions
|
@ -2,7 +2,7 @@ import type { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/bas
|
|||
import type { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
|
||||
import React, { FC } from 'react';
|
||||
import { useGetGenres } from 'hooks/useFetchItems';
|
||||
import globalize from 'lib/globalize';
|
||||
import NoItemsMessage from 'components/common/NoItemsMessage';
|
||||
import Loading from 'components/loading/LoadingComponent';
|
||||
import GenresSectionContainer from './GenresSectionContainer';
|
||||
import type { ParentId } from 'types/library';
|
||||
|
@ -25,27 +25,18 @@ const GenresItemsContainer: FC<GenresItemsContainerProps> = ({
|
|||
}
|
||||
|
||||
if (!genresResult?.Items?.length) {
|
||||
return (
|
||||
<div className='noItemsMessage centerMessage'>
|
||||
<h1>{globalize.translate('MessageNothingHere')}</h1>
|
||||
<p>{globalize.translate('MessageNoGenresAvailable')}</p>
|
||||
</div>
|
||||
);
|
||||
return <NoItemsMessage message='MessageNoGenresAvailable' />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{genresResult.Items.map((genre) => (
|
||||
<GenresSectionContainer
|
||||
key={genre.Id}
|
||||
collectionType={collectionType}
|
||||
parentId={parentId}
|
||||
itemType={itemType}
|
||||
genre={genre}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
return genresResult.Items.map((genre) => (
|
||||
<GenresSectionContainer
|
||||
key={genre.Id}
|
||||
collectionType={collectionType}
|
||||
parentId={parentId}
|
||||
itemType={itemType}
|
||||
genre={genre}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
export default GenresItemsContainer;
|
||||
|
|
|
@ -8,7 +8,7 @@ import React, { type FC } from 'react';
|
|||
import { useGetItems } from 'hooks/useFetchItems';
|
||||
import Loading from 'components/loading/LoadingComponent';
|
||||
import { appRouter } from 'components/router/appRouter';
|
||||
import SectionContainer from './SectionContainer';
|
||||
import SectionContainer from 'components/common/SectionContainer';
|
||||
import { CardShape } from 'utils/card';
|
||||
import type { ParentId } from 'types/library';
|
||||
import type { ItemDto } from 'types/base/models/item-dto';
|
||||
|
@ -59,9 +59,12 @@ const GenresSectionContainer: FC<GenresSectionContainerProps> = ({
|
|||
}
|
||||
|
||||
return <SectionContainer
|
||||
sectionTitle={genre.Name || ''}
|
||||
items={itemsResult?.Items || []}
|
||||
url={getRouteUrl(genre)}
|
||||
key={genre.Name}
|
||||
sectionHeaderProps={{
|
||||
title: genre.Name || '',
|
||||
url: getRouteUrl(genre)
|
||||
}}
|
||||
items={itemsResult?.Items}
|
||||
cardOptions={{
|
||||
scalable: true,
|
||||
overlayPlayButton: true,
|
||||
|
|
|
@ -181,7 +181,7 @@ const ItemsView: FC<ItemsViewProps> = ({
|
|||
|
||||
const getItems = useCallback(() => {
|
||||
if (!itemsResult?.Items?.length) {
|
||||
return <NoItemsMessage noItemsMessage={noItemsMessage} />;
|
||||
return <NoItemsMessage message={noItemsMessage} />;
|
||||
}
|
||||
|
||||
if (libraryViewSettings.ViewMode === ViewMode.ListView) {
|
||||
|
|
|
@ -3,7 +3,8 @@ import { useGetProgramsSectionsWithItems, useGetTimers } from 'hooks/useFetchIte
|
|||
import { appRouter } from 'components/router/appRouter';
|
||||
import globalize from 'lib/globalize';
|
||||
import Loading from 'components/loading/LoadingComponent';
|
||||
import SectionContainer from './SectionContainer';
|
||||
import NoItemsMessage from 'components/common/NoItemsMessage';
|
||||
import SectionContainer from 'components/common/SectionContainer';
|
||||
import { CardShape } from 'utils/card';
|
||||
import type { ParentId } from 'types/library';
|
||||
import type { Section, SectionType } from 'types/sections';
|
||||
|
@ -30,14 +31,7 @@ const ProgramsSectionView: FC<ProgramsSectionViewProps> = ({
|
|||
}
|
||||
|
||||
if (!sectionsWithItems?.length && !upcomingRecordings?.length) {
|
||||
return (
|
||||
<div className='noItemsMessage centerMessage'>
|
||||
<h1>{globalize.translate('MessageNothingHere')}</h1>
|
||||
<p>
|
||||
{globalize.translate('MessageNoItemsAvailable')}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
return <NoItemsMessage />;
|
||||
}
|
||||
|
||||
const getRouteUrl = (section: Section) => {
|
||||
|
@ -58,23 +52,33 @@ const ProgramsSectionView: FC<ProgramsSectionViewProps> = ({
|
|||
{sectionsWithItems?.map(({ section, items }) => (
|
||||
<SectionContainer
|
||||
key={section.type}
|
||||
sectionTitle={globalize.translate(section.name)}
|
||||
items={items ?? []}
|
||||
url={getRouteUrl(section)}
|
||||
reloadItems={refetch}
|
||||
sectionHeaderProps={{
|
||||
title: globalize.translate(section.name),
|
||||
url: getRouteUrl(section)
|
||||
}}
|
||||
itemsContainerProps={{
|
||||
queryKey: ['ProgramSectionWithItems'],
|
||||
reloadItems: refetch
|
||||
}}
|
||||
items={items}
|
||||
cardOptions={{
|
||||
...section.cardOptions,
|
||||
queryKey: ['ProgramSectionWithItems']
|
||||
}}
|
||||
/>
|
||||
|
||||
))}
|
||||
|
||||
{upcomingRecordings?.map((group) => (
|
||||
<SectionContainer
|
||||
key={group.name}
|
||||
sectionTitle={group.name}
|
||||
items={group.timerInfo ?? []}
|
||||
sectionHeaderProps={{
|
||||
title: group.name
|
||||
}}
|
||||
itemsContainerProps={{
|
||||
queryKey: ['Timers'],
|
||||
reloadItems: refetch
|
||||
}}
|
||||
items={group.timerInfo }
|
||||
cardOptions={{
|
||||
queryKey: ['Timers'],
|
||||
shape: CardShape.BackdropOverflow,
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import ItemsContainer from 'elements/emby-itemscontainer/ItemsContainer';
|
||||
import Scroller from 'elements/emby-scroller/Scroller';
|
||||
import LinkButton from 'elements/emby-button/LinkButton';
|
||||
import Cards from 'components/cardbuilder/Card/Cards';
|
||||
import type { CardOptions } from 'types/cardOptions';
|
||||
import type { ItemDto } from 'types/base/models/item-dto';
|
||||
|
||||
interface SectionContainerProps {
|
||||
url?: string;
|
||||
sectionTitle: string;
|
||||
items: ItemDto[];
|
||||
cardOptions: CardOptions;
|
||||
reloadItems?: () => void;
|
||||
}
|
||||
|
||||
const SectionContainer: FC<SectionContainerProps> = ({
|
||||
sectionTitle,
|
||||
url,
|
||||
items,
|
||||
cardOptions,
|
||||
reloadItems
|
||||
}) => {
|
||||
return (
|
||||
<div className='verticalSection'>
|
||||
<div className='sectionTitleContainer sectionTitleContainer-cards padded-left'>
|
||||
{url && items.length > 5 ? (
|
||||
<LinkButton
|
||||
className='more button-flat button-flat-mini sectionTitleTextButton btnMoreFromGenre'
|
||||
href={url}
|
||||
>
|
||||
<h2 className='sectionTitle sectionTitle-cards'>
|
||||
{sectionTitle}
|
||||
</h2>
|
||||
<span
|
||||
className='material-icons chevron_right'
|
||||
aria-hidden='true'
|
||||
></span>
|
||||
</LinkButton>
|
||||
) : (
|
||||
<h2 className='sectionTitle sectionTitle-cards'>
|
||||
{sectionTitle}
|
||||
</h2>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Scroller
|
||||
className='padded-top-focusscale padded-bottom-focusscale'
|
||||
isMouseWheelEnabled={false}
|
||||
isCenterFocusEnabled={true}
|
||||
>
|
||||
<ItemsContainer
|
||||
className='itemsContainer scrollSlider focuscontainer-x'
|
||||
reloadItems={reloadItems}
|
||||
queryKey={cardOptions.queryKey}
|
||||
>
|
||||
<Cards items={items} cardOptions={cardOptions} />
|
||||
</ItemsContainer>
|
||||
</Scroller>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SectionContainer;
|
|
@ -8,7 +8,8 @@ import {
|
|||
import { appRouter } from 'components/router/appRouter';
|
||||
import globalize from 'lib/globalize';
|
||||
import Loading from 'components/loading/LoadingComponent';
|
||||
import SectionContainer from './SectionContainer';
|
||||
import NoItemsMessage from 'components/common/NoItemsMessage';
|
||||
import SectionContainer from '../../../../components/common/SectionContainer';
|
||||
import { CardShape } from 'utils/card';
|
||||
import type { ParentId } from 'types/library';
|
||||
import type { Section, SectionType } from 'types/sections';
|
||||
|
@ -38,12 +39,7 @@ const SuggestionsSectionView: FC<SuggestionsSectionViewProps> = ({
|
|||
}
|
||||
|
||||
if (!sectionsWithItems?.length && !movieRecommendationsItems?.length) {
|
||||
return (
|
||||
<div className='noItemsMessage centerMessage'>
|
||||
<h1>{globalize.translate('MessageNothingHere')}</h1>
|
||||
<p>{globalize.translate('MessageNoItemsAvailable')}</p>
|
||||
</div>
|
||||
);
|
||||
return <NoItemsMessage />;
|
||||
}
|
||||
|
||||
const getRouteUrl = (section: Section) => {
|
||||
|
@ -96,9 +92,14 @@ const SuggestionsSectionView: FC<SuggestionsSectionViewProps> = ({
|
|||
{sectionsWithItems?.map(({ section, items }) => (
|
||||
<SectionContainer
|
||||
key={section.type}
|
||||
sectionTitle={globalize.translate(section.name)}
|
||||
items={items ?? []}
|
||||
url={getRouteUrl(section)}
|
||||
sectionHeaderProps={{
|
||||
title: globalize.translate(section.name),
|
||||
url: getRouteUrl(section)
|
||||
}}
|
||||
itemsContainerProps={{
|
||||
queryKey: ['SuggestionSectionWithItems']
|
||||
}}
|
||||
items={items}
|
||||
cardOptions={{
|
||||
...section.cardOptions,
|
||||
queryKey: ['SuggestionSectionWithItems'],
|
||||
|
@ -114,8 +115,13 @@ const SuggestionsSectionView: FC<SuggestionsSectionViewProps> = ({
|
|||
<SectionContainer
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
key={`${recommendation.CategoryId}-${index}`} // use a unique id return value may have duplicate id
|
||||
sectionTitle={getRecommendationTittle(recommendation)}
|
||||
items={(recommendation.Items as ItemDto[]) ?? []}
|
||||
sectionHeaderProps={{
|
||||
title: getRecommendationTittle(recommendation)
|
||||
}}
|
||||
itemsContainerProps={{
|
||||
queryKey: ['MovieRecommendations']
|
||||
}}
|
||||
items={recommendation.Items as ItemDto[]}
|
||||
cardOptions={{
|
||||
queryKey: ['MovieRecommendations'],
|
||||
shape: CardShape.PortraitOverflow,
|
||||
|
|
|
@ -1,49 +1,44 @@
|
|||
import React, { type FC } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import { useGetGroupsUpcomingEpisodes } from 'hooks/useFetchItems';
|
||||
import Loading from 'components/loading/LoadingComponent';
|
||||
import globalize from 'lib/globalize';
|
||||
import SectionContainer from './SectionContainer';
|
||||
import NoItemsMessage from 'components/common/NoItemsMessage';
|
||||
import SectionContainer from 'components/common/SectionContainer';
|
||||
import { CardShape } from 'utils/card';
|
||||
import type { LibraryViewProps } from 'types/library';
|
||||
|
||||
const UpcomingView: FC<LibraryViewProps> = ({ parentId }) => {
|
||||
const { isLoading, data: groupsUpcomingEpisodes } = useGetGroupsUpcomingEpisodes(parentId);
|
||||
const { isLoading, data: groupsUpcomingEpisodes } =
|
||||
useGetGroupsUpcomingEpisodes(parentId);
|
||||
|
||||
if (isLoading) return <Loading />;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{!groupsUpcomingEpisodes?.length ? (
|
||||
<div className='noItemsMessage centerMessage'>
|
||||
<h1>{globalize.translate('MessageNothingHere')}</h1>
|
||||
<p>
|
||||
{globalize.translate(
|
||||
'MessagePleaseEnsureInternetMetadata'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
groupsUpcomingEpisodes?.map((group) => (
|
||||
<SectionContainer
|
||||
key={group.name}
|
||||
sectionTitle={group.name}
|
||||
items={group.items ?? []}
|
||||
cardOptions={{
|
||||
shape: CardShape.BackdropOverflow,
|
||||
showLocationTypeIndicator: false,
|
||||
showParentTitle: true,
|
||||
preferThumb: true,
|
||||
lazy: true,
|
||||
showDetailsMenu: true,
|
||||
missingIndicator: false,
|
||||
cardLayout: false
|
||||
}}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
if (!groupsUpcomingEpisodes?.length) {
|
||||
return <NoItemsMessage message='MessagePleaseEnsureInternetMetadata' />;
|
||||
}
|
||||
|
||||
return groupsUpcomingEpisodes?.map((group) => (
|
||||
<SectionContainer
|
||||
key={group.name}
|
||||
sectionHeaderProps={{
|
||||
title: group.name
|
||||
}}
|
||||
itemsContainerProps={{
|
||||
queryKey: ['GroupsUpcomingEpisodes']
|
||||
}}
|
||||
items={group.items}
|
||||
cardOptions={{
|
||||
shape: CardShape.BackdropOverflow,
|
||||
showLocationTypeIndicator: false,
|
||||
showParentTitle: true,
|
||||
preferThumb: true,
|
||||
lazy: true,
|
||||
showDetailsMenu: true,
|
||||
missingIndicator: false,
|
||||
cardLayout: false,
|
||||
queryKey: ['GroupsUpcomingEpisodes']
|
||||
}}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
export default UpcomingView;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue