1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

refactor: extract reusable component

This commit is contained in:
grafixeyehero 2023-10-26 02:05:08 +03:00
parent 4882d9c8cc
commit d370afd0b2
17 changed files with 437 additions and 216 deletions

View file

@ -9,8 +9,8 @@ import { ParentId } from 'types/library';
interface GenresItemsContainerProps {
parentId: ParentId;
collectionType: CollectionType;
itemType: BaseItemKind;
collectionType: CollectionType | undefined;
itemType: BaseItemKind[];
}
const GenresItemsContainer: FC<GenresItemsContainerProps> = ({

View file

@ -16,8 +16,8 @@ import { ParentId } from 'types/library';
interface GenresSectionContainerProps {
parentId: ParentId;
collectionType: CollectionType;
itemType: BaseItemKind;
collectionType: CollectionType | undefined;
itemType: BaseItemKind[];
genre: BaseItemDto;
}
@ -31,7 +31,7 @@ const GenresSectionContainer: FC<GenresSectionContainerProps> = ({
return {
sortBy: [ItemSortBy.Random],
sortOrder: [SortOrder.Ascending],
includeItemTypes: [itemType],
includeItemTypes: itemType,
recursive: true,
fields: [
ItemFields.PrimaryImageAspectRatio,
@ -70,9 +70,9 @@ const GenresSectionContainer: FC<GenresSectionContainerProps> = ({
showTitle: true,
centerText: true,
cardLayout: false,
shape: itemType === BaseItemKind.MusicAlbum ? 'overflowSquare' : 'overflowPortrait',
showParentTitle: itemType === BaseItemKind.MusicAlbum,
showYear: itemType !== BaseItemKind.MusicAlbum
shape: collectionType === CollectionType.Music ? 'overflowSquare' : 'overflowPortrait',
showParentTitle: collectionType === CollectionType.Music,
showYear: collectionType !== CollectionType.Music
}}
/>;
};

View file

@ -0,0 +1,23 @@
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
import React, { FC } from 'react';
import GenresItemsContainer from './GenresItemsContainer';
import { ParentId } from 'types/library';
import { CollectionType } from 'types/collectionType';
interface GenresViewProps {
parentId: ParentId;
collectionType: CollectionType | undefined;
itemType: BaseItemKind[];
}
const GenresView: FC<GenresViewProps> = ({ parentId, collectionType, itemType }) => {
return (
<GenresItemsContainer
parentId={parentId}
collectionType={collectionType}
itemType={itemType}
/>
);
};
export default GenresView;

View file

@ -0,0 +1,65 @@
import React, { FC } from 'react';
import SuggestionsView from './SuggestionsView';
import UpcomingView from './UpcomingView';
import GenresView from './GenresView';
import ItemsView from './ItemsView';
import { LibraryTab } from 'types/libraryTab';
import { ParentId } from 'types/library';
import { LibraryTabContent } from 'types/libraryTabContent';
interface PageTabContentProps {
parentId: ParentId;
currentTab: LibraryTabContent;
}
const PageTabContent: FC<PageTabContentProps> = ({ parentId, currentTab }) => {
if (currentTab.viewType === LibraryTab.Suggestions) {
return (
<SuggestionsView
parentId={parentId}
suggestionSectionViews={
currentTab.sectionsType?.suggestionSectionsView
}
isMovieRecommendations={
currentTab.sectionsType?.isMovieRecommendations
}
/>
);
}
if (currentTab.viewType === LibraryTab.Upcoming) {
return <UpcomingView parentId={parentId} />;
}
if (currentTab.viewType === LibraryTab.Genres) {
return (
<GenresView
parentId={parentId}
collectionType={currentTab.collectionType}
itemType={currentTab.itemType || []}
/>
);
}
return (
<ItemsView
viewType={currentTab.viewType}
parentId={parentId}
collectionType={currentTab.collectionType}
isBtnPlayAllEnabled={currentTab.isBtnPlayAllEnabled}
isBtnQueueEnabled={currentTab.isBtnQueueEnabled}
isBtnShuffleEnabled={currentTab.isBtnShuffleEnabled}
isBtnNewCollectionEnabled={currentTab.isBtnNewCollectionEnabled}
isBtnFilterEnabled={currentTab.isBtnFilterEnabled}
isBtnGridListEnabled={currentTab.isBtnGridListEnabled}
isBtnSortEnabled={currentTab.isBtnSortEnabled}
isAlphabetPickerEnabled={currentTab.isAlphabetPickerEnabled}
itemType={currentTab.itemType || []}
noItemsMessage={
currentTab.noItemsMessage || 'MessageNoItemsAvailable'
}
/>
);
};
export default PageTabContent;

View file

@ -0,0 +1,46 @@
import React, { FC } from 'react';
import { useGetMovieRecommendations } from 'hooks/useFetchItems';
import Loading from 'components/loading/LoadingComponent';
import globalize from 'scripts/globalize';
import RecommendationContainer from './RecommendationContainer';
import { ParentId } from 'types/library';
interface RecommendationItemsContainerProps {
parentId?: ParentId;
}
const RecommendationItemsContainer: FC<RecommendationItemsContainerProps> = ({
parentId
}) => {
const { isLoading, data: movieRecommendationsItems } =
useGetMovieRecommendations(parentId);
if (isLoading) return <Loading />;
if (!movieRecommendationsItems?.length) {
return (
<div className='noItemsMessage centerMessage'>
<h1>{globalize.translate('MessageNothingHere')}</h1>
<p>
{globalize.translate('MessageNoMovieSuggestionsAvailable')}
</p>
</div>
);
}
return (
<>
{movieRecommendationsItems.map((recommendation, index) => {
return (
<RecommendationContainer
// eslint-disable-next-line react/no-array-index-key
key={`${recommendation.CategoryId}-${index}`} // use a unique id return value may have duplicate id
recommendation={recommendation}
/>
);
})}
</>
);
};
export default RecommendationItemsContainer;

View file

@ -0,0 +1,33 @@
import React, { FC } from 'react';
import { Box } from '@mui/material';
import SuggestionsItemsContainer from './SuggestionsItemsContainer';
import RecommendationItemsContainer from './RecommendationItemsContainer';
import { ParentId } from 'types/library';
import { SectionsView } from 'types/suggestionsSections';
interface SuggestionsViewProps {
parentId: ParentId;
suggestionSectionViews: SectionsView[] | undefined;
isMovieRecommendations: boolean | undefined;
}
const SuggestionsView: FC<SuggestionsViewProps> = ({
parentId,
suggestionSectionViews = [],
isMovieRecommendations = false
}) => {
return (
<Box>
<SuggestionsItemsContainer
parentId={parentId}
sectionsView={suggestionSectionViews}
/>
{isMovieRecommendations && (
<RecommendationItemsContainer parentId={parentId} />
)}
</Box>
);
};
export default SuggestionsView;

View file

@ -0,0 +1,48 @@
import React, { FC } from 'react';
import Box from '@mui/material/Box';
import { useGetGroupsUpcomingEpisodes } from 'hooks/useFetchItems';
import Loading from 'components/loading/LoadingComponent';
import globalize from 'scripts/globalize';
import SectionContainer from './SectionContainer';
import { LibraryViewProps } from 'types/library';
const UpcomingView: FC<LibraryViewProps> = ({ 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: 'overflowBackdrop',
showLocationTypeIndicator: false,
showParentTitle: true,
preferThumb: true,
lazy: true,
showDetailsMenu: true,
missingIndicator: false,
cardLayout: false
}}
/>
))
)}
</Box>
);
};
export default UpcomingView;

View file

@ -1,24 +0,0 @@
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
import React, { FC } from 'react';
import ItemsView from '../../components/library/ItemsView';
import { LibraryViewProps } from 'types/library';
import { CollectionType } from 'types/collectionType';
import { LibraryTab } from 'types/libraryTab';
const CollectionsView: FC<LibraryViewProps> = ({ parentId }) => {
return (
<ItemsView
viewType={LibraryTab.Collections}
parentId={parentId}
collectionType={CollectionType.Movies}
isBtnFilterEnabled={false}
isBtnNewCollectionEnabled={true}
isAlphabetPickerEnabled={false}
itemType={[BaseItemKind.BoxSet]}
noItemsMessage='MessageNoCollectionsAvailable'
/>
);
};
export default CollectionsView;

View file

@ -1,19 +0,0 @@
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
import React, { FC } from 'react';
import ItemsView from '../../components/library/ItemsView';
import { LibraryViewProps } from 'types/library';
import { LibraryTab } from 'types/libraryTab';
const FavoritesView: FC<LibraryViewProps> = ({ parentId }) => {
return (
<ItemsView
viewType={LibraryTab.Favorites}
parentId={parentId}
itemType={[BaseItemKind.Movie]}
noItemsMessage='MessageNoFavoritesAvailable'
/>
);
};
export default FavoritesView;

View file

@ -1,17 +0,0 @@
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
import React, { FC } from 'react';
import GenresItemsContainer from '../../components/library/GenresItemsContainer';
import { LibraryViewProps } from 'types/library';
import { CollectionType } from 'types/collectionType';
const GenresView: FC<LibraryViewProps> = ({ parentId }) => {
return (
<GenresItemsContainer
parentId={parentId}
collectionType={CollectionType.Movies}
itemType={BaseItemKind.Movie}
/>
);
};
export default GenresView;

View file

@ -1,22 +0,0 @@
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
import React, { FC } from 'react';
import ItemsView from '../../components/library/ItemsView';
import { LibraryViewProps } from 'types/library';
import { CollectionType } from 'types/collectionType';
import { LibraryTab } from 'types/libraryTab';
const MoviesView: FC<LibraryViewProps> = ({ parentId }) => {
return (
<ItemsView
viewType={LibraryTab.Movies}
parentId={parentId}
collectionType={CollectionType.Movies}
isBtnShuffleEnabled={true}
itemType={[BaseItemKind.Movie]}
noItemsMessage='MessageNoItemsAvailable'
/>
);
};
export default MoviesView;

View file

@ -1,52 +0,0 @@
import React, { FC } from 'react';
import { useGetMovieRecommendations } from 'hooks/useFetchItems';
import globalize from 'scripts/globalize';
import Loading from 'components/loading/LoadingComponent';
import RecommendationContainer from '../../components/library/RecommendationContainer';
import SuggestionsItemsContainer from '../../components/library/SuggestionsItemsContainer';
import { LibraryViewProps } from 'types/library';
import { SectionsView } from 'types/suggestionsSections';
const SuggestionsView: FC<LibraryViewProps> = ({ parentId }) => {
const {
isLoading,
data: movieRecommendationsItems
} = useGetMovieRecommendations(parentId);
if (isLoading) {
return <Loading />;
}
return (
<>
<SuggestionsItemsContainer
parentId={parentId}
sectionsView={[SectionsView.ContinueWatchingMovies, SectionsView.LatestMovies]}
/>
{!movieRecommendationsItems?.length ? (
<div className='noItemsMessage centerMessage'>
<h1>{globalize.translate('MessageNothingHere')}</h1>
<p>
{globalize.translate(
'MessageNoMovieSuggestionsAvailable'
)}
</p>
</div>
) : (
movieRecommendationsItems.map((recommendation, index) => {
return (
<RecommendationContainer
// eslint-disable-next-line react/no-array-index-key
key={`${recommendation.CategoryId}-${index}`} // use a unique id return value may have duplicate id
recommendation={recommendation}
/>
);
})
)}
</>
);
};
export default SuggestionsView;

View file

@ -1,19 +0,0 @@
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
import React, { FC } from 'react';
import ItemsView from '../../components/library/ItemsView';
import { LibraryViewProps } from 'types/library';
import { LibraryTab } from 'types/libraryTab';
const TrailersView: FC<LibraryViewProps> = ({ parentId }) => {
return (
<ItemsView
viewType={LibraryTab.Trailers}
parentId={parentId}
itemType={[BaseItemKind.Trailer]}
noItemsMessage='MessageNoTrailersFound'
/>
);
};
export default TrailersView;

View file

@ -1,55 +1,72 @@
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
import React, { FC } from 'react';
import { useLocation, useSearchParams } from 'react-router-dom';
import { getDefaultTabIndex } from '../../components/tabs/tabRoutes';
import useCurrentTab from 'hooks/useCurrentTab';
import Page from 'components/Page';
import CollectionsView from './CollectionsView';
import FavoritesView from './FavoritesView';
import GenresView from './GenresView';
import MoviesView from './MoviesView';
import SuggestionsView from './SuggestionsView';
import TrailersView from './TrailersView';
import PageTabContent from '../../components/library/PageTabContent';
import { LibraryTab } from 'types/libraryTab';
import { CollectionType } from 'types/collectionType';
import { LibraryTabContent, LibraryTabMapping } from 'types/libraryTabContent';
import { SectionsView } from 'types/suggestionsSections';
const moviesTabContent: LibraryTabContent = {
viewType: LibraryTab.Movies,
collectionType: CollectionType.Movies,
isBtnShuffleEnabled: true,
itemType: [BaseItemKind.Movie]
};
const collectionsTabContent: LibraryTabContent = {
viewType: LibraryTab.Collections,
collectionType: CollectionType.Movies,
isBtnFilterEnabled: false,
isBtnNewCollectionEnabled: true,
isAlphabetPickerEnabled: false,
itemType: [BaseItemKind.BoxSet],
noItemsMessage: 'MessageNoCollectionsAvailable'
};
const favoritesTabContent: LibraryTabContent = {
viewType: LibraryTab.Favorites,
collectionType: CollectionType.Movies,
itemType: [BaseItemKind.Movie]
};
const trailersTabContent: LibraryTabContent = {
viewType: LibraryTab.Trailers,
itemType: [BaseItemKind.Trailer],
noItemsMessage: 'MessageNoTrailersFound'
};
const suggestionsTabContent: LibraryTabContent = {
viewType: LibraryTab.Suggestions,
collectionType: CollectionType.Movies,
sectionsType: {
suggestionSectionsView: [
SectionsView.ContinueWatchingMovies,
SectionsView.LatestMovies
],
isMovieRecommendations: true
}
};
const genresTabContent: LibraryTabContent = {
viewType: LibraryTab.Genres,
collectionType: CollectionType.Movies,
itemType: [BaseItemKind.Movie]
};
const moviesTabMapping: LibraryTabMapping = {
0: moviesTabContent,
1: suggestionsTabContent,
2: trailersTabContent,
3: favoritesTabContent,
4: collectionsTabContent,
5: genresTabContent
};
const Movies: FC = () => {
const location = useLocation();
const [ searchParams ] = useSearchParams();
const searchParamsParentId = searchParams.get('topParentId');
const searchParamsTab = searchParams.get('tab');
const currentTabIndex = searchParamsTab !== null ? parseInt(searchParamsTab, 10) :
getDefaultTabIndex(location.pathname, searchParamsParentId);
const getTabComponent = (index: number) => {
if (index == null) {
throw new Error('index cannot be null');
}
let component;
switch (index) {
case 1:
component = <SuggestionsView parentId={searchParamsParentId} />;
break;
case 2:
component = <TrailersView parentId={searchParamsParentId} />;
break;
case 3:
component = <FavoritesView parentId={searchParamsParentId} />;
break;
case 4:
component = <CollectionsView parentId={searchParamsParentId} />;
break;
case 5:
component = <GenresView parentId={searchParamsParentId} />;
break;
default:
component = <MoviesView parentId={searchParamsParentId} />;
}
return component;
};
const { searchParamsParentId, currentTabIndex } = useCurrentTab();
const currentTab = moviesTabMapping[currentTabIndex];
return (
<Page
@ -57,8 +74,11 @@ const Movies: FC = () => {
className='mainAnimatedPage libraryPage backdropPage collectionEditorPage pageWithAbsoluteTabs withTabs'
backDropType='movie'
>
{getTabComponent(currentTabIndex)}
<PageTabContent
key={`${currentTab.viewType} - ${searchParamsParentId}`}
currentTab={currentTab}
parentId={searchParamsParentId}
/>
</Page>
);
};