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

refactor: suggestionview and genresview

This commit is contained in:
grafixeyehero 2023-06-07 03:38:39 +03:00
parent 13aa3c9efa
commit 17e8ccc93a
27 changed files with 1253 additions and 602 deletions

View file

@ -0,0 +1,52 @@
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
import React, { FC } from 'react';
import { useGetGenres } from 'hooks/useFetchItems';
import globalize from 'scripts/globalize';
import Loading from 'components/loading/LoadingComponent';
import GenresSectionContainer from './GenresSectionContainer';
import { CollectionType } from 'types/collectionType';
interface GenresItemsContainerProps {
parentId?: string | null;
collectionType?: CollectionType;
itemType: BaseItemKind;
}
const GenresItemsContainer: FC<GenresItemsContainerProps> = ({
parentId,
collectionType,
itemType
}) => {
const { isLoading, data: genresResult } = useGetGenres(
parentId,
itemType
);
if (isLoading) {
return <Loading />;
}
return (
<>
{!genresResult?.Items?.length ? (
<div className='noItemsMessage centerMessage'>
<h1>{globalize.translate('MessageNothingHere')}</h1>
<p>{globalize.translate('MessageNoGenresAvailable')}</p>
</div>
) : (
genresResult?.Items
&& genresResult?.Items.map((genre) => (
<GenresSectionContainer
key={genre.Id}
collectionType={collectionType}
parentId={parentId}
itemType={itemType}
genre={genre}
/>
))
)}
</>
);
};
export default GenresItemsContainer;

View file

@ -0,0 +1,79 @@
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields';
import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type';
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by';
import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order';
import escapeHTML from 'escape-html';
import React, { 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 { CollectionType } from 'types/collectionType';
interface GenresSectionContainerProps {
parentId?: string | null;
collectionType?: CollectionType;
itemType: BaseItemKind;
genre: BaseItemDto;
}
const GenresSectionContainer: FC<GenresSectionContainerProps> = ({
parentId,
collectionType,
itemType,
genre
}) => {
const getParametersOptions = () => {
return {
sortBy: [ItemSortBy.Random],
sortOrder: [SortOrder.Ascending],
includeItemTypes: [itemType],
recursive: true,
fields: [
ItemFields.PrimaryImageAspectRatio,
ItemFields.MediaSourceCount,
ItemFields.BasicSyncInfo
],
imageTypeLimit: 1,
enableImageTypes: [ImageType.Primary],
limit: 25,
genreIds: genre.Id ? [genre.Id] : undefined,
enableTotalRecordCount: false,
parentId: parentId ?? undefined
};
};
const { isLoading, data: itemsResult } = useGetItems(getParametersOptions());
const getRouteUrl = (item: BaseItemDto) => {
return appRouter.getRouteUrl(item, {
context: collectionType,
parentId: parentId
});
};
if (isLoading) {
return <Loading />;
}
return <SectionContainer
sectionTitle={escapeHTML(genre.Name)}
items={itemsResult?.Items || []}
url={getRouteUrl(genre)}
cardOptions={{
scalable: true,
overlayPlayButton: true,
showTitle: true,
centerText: true,
cardLayout: false,
shape: itemType === BaseItemKind.MusicAlbum ? 'overflowSquare' : 'overflowPortrait',
showParentTitle: itemType === BaseItemKind.MusicAlbum ? true : false,
showYear: itemType === BaseItemKind.MusicAlbum ? false : true
}}
/>;
};
export default GenresSectionContainer;

View file

@ -0,0 +1,61 @@
import { RecommendationDto, RecommendationType } from '@jellyfin/sdk/lib/generated-client';
import React, { FC } from 'react';
import globalize from 'scripts/globalize';
import escapeHTML from 'escape-html';
import SectionContainer from './SectionContainer';
interface RecommendationContainerProps {
recommendation?: RecommendationDto;
}
const RecommendationContainer: FC<RecommendationContainerProps> = ({
recommendation = {}
}) => {
let title = '';
switch (recommendation.RecommendationType) {
case RecommendationType.SimilarToRecentlyPlayed:
title = globalize.translate(
'RecommendationBecauseYouWatched',
recommendation.BaselineItemName
);
break;
case RecommendationType.SimilarToLikedItem:
title = globalize.translate(
'RecommendationBecauseYouLike',
recommendation.BaselineItemName
);
break;
case RecommendationType.HasDirectorFromRecentlyPlayed:
case RecommendationType.HasLikedDirector:
title = globalize.translate(
'RecommendationDirectedBy',
recommendation.BaselineItemName
);
break;
case RecommendationType.HasActorFromRecentlyPlayed:
case RecommendationType.HasLikedActor:
title = globalize.translate(
'RecommendationStarring',
recommendation.BaselineItemName
);
break;
}
return (
<SectionContainer
sectionTitle={escapeHTML(title)}
items={recommendation.Items || []}
cardOptions={{
shape: 'overflowPortrait',
showYear: true
}}
/>
);
};
export default RecommendationContainer;

View file

@ -0,0 +1,73 @@
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
import React, { FC, useEffect, useRef } from 'react';
import cardBuilder from 'components/cardbuilder/cardBuilder';
import ItemsContainerElement from 'elements/ItemsContainerElement';
import Scroller from 'elements/emby-scroller/Scroller';
import LinkButton from 'elements/emby-button/LinkButton';
import imageLoader from 'components/images/imageLoader';
import { CardOptions } from 'types/cardOptions';
interface SectionContainerProps {
url?: string;
sectionTitle: string;
items: BaseItemDto[];
cardOptions: CardOptions;
}
const SectionContainer: FC<SectionContainerProps> = ({
sectionTitle,
url,
items,
cardOptions
}) => {
const element = useRef<HTMLDivElement>(null);
useEffect(() => {
const itemsContainer = element.current?.querySelector('.itemsContainer');
cardBuilder.buildCards(items, {
itemsContainer: itemsContainer,
parentContainer: element.current,
...cardOptions
});
imageLoader.lazyChildren(itemsContainer);
}, [cardOptions, items]);
return (
<div ref={element} className='verticalSection hide'>
<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}
>
<ItemsContainerElement className='itemsContainer scrollSlider focuscontainer-x' />
</Scroller>
</div>
);
};
export default SectionContainer;

View file

@ -0,0 +1,206 @@
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by';
import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order';
import React, { FC } from 'react';
import * as userSettings from 'scripts/settings/userSettings';
import SuggestionsSectionContainer from './SuggestionsSectionContainer';
import { Sections, SectionsView, SectionsViewType } from 'types/suggestionsSections';
const getSuggestionsSections = (): Sections[] => {
return [
{
name: 'HeaderContinueWatching',
viewType: SectionsViewType.ResumeItems,
type: 'Movie',
view: SectionsView.ContinueWatchingMovies,
parametersOptions: {
includeItemTypes: [BaseItemKind.Movie]
},
cardOptions: {
scalable: true,
overlayPlayButton: true,
showTitle: true,
centerText: true,
cardLayout: false,
preferThumb: true,
shape: 'overflowBackdrop',
showYear: true
}
},
{
name: 'HeaderLatestMovies',
viewType: SectionsViewType.LatestMedia,
type: 'Movie',
view: SectionsView.LatestMovies,
parametersOptions: {
includeItemTypes: [BaseItemKind.Movie]
},
cardOptions: {
scalable: true,
overlayPlayButton: true,
showTitle: true,
centerText: true,
cardLayout: false,
shape: 'overflowPortrait',
showYear: true
}
},
{
name: 'HeaderContinueWatching',
viewType: SectionsViewType.ResumeItems,
type: 'Episode',
view: SectionsView.ContinueWatchingEpisode,
parametersOptions: {
includeItemTypes: [BaseItemKind.Episode]
},
cardOptions: {
scalable: true,
overlayPlayButton: true,
showTitle: true,
centerText: true,
cardLayout: false,
shape: 'overflowBackdrop',
preferThumb: true,
inheritThumb:
!userSettings.useEpisodeImagesInNextUpAndResume(undefined),
showYear: true
}
},
{
name: 'HeaderLatestEpisodes',
viewType: SectionsViewType.LatestMedia,
type: 'Episode',
view: SectionsView.LatestEpisode,
parametersOptions: {
includeItemTypes: [BaseItemKind.Episode]
},
cardOptions: {
scalable: true,
overlayPlayButton: true,
showTitle: true,
centerText: true,
cardLayout: false,
shape: 'overflowBackdrop',
preferThumb: true,
showSeriesYear: true,
showParentTitle: true,
overlayText: false,
showUnplayedIndicator: false,
showChildCountIndicator: true,
lazy: true,
lines: 2
}
},
{
name: 'NextUp',
viewType: SectionsViewType.NextUp,
type: 'nextup',
view: SectionsView.NextUp,
cardOptions: {
scalable: true,
overlayPlayButton: true,
showTitle: true,
centerText: true,
cardLayout: false,
shape: 'overflowBackdrop',
preferThumb: true,
inheritThumb:
!userSettings.useEpisodeImagesInNextUpAndResume(undefined),
showParentTitle: true,
overlayText: false
}
},
{
name: 'HeaderLatestMusic',
viewType: SectionsViewType.LatestMedia,
type: 'Audio',
view: SectionsView.LatestMusic,
parametersOptions: {
includeItemTypes: [BaseItemKind.Audio]
},
cardOptions: {
showUnplayedIndicator: false,
shape: 'overflowSquare',
showTitle: true,
showParentTitle: true,
lazy: true,
centerText: true,
overlayPlayButton: true,
cardLayout: false,
coverImage: true
}
},
{
name: 'HeaderRecentlyPlayed',
type: 'Audio',
view: SectionsView.RecentlyPlayedMusic,
parametersOptions: {
sortBy: [ItemSortBy.DatePlayed],
sortOrder: [SortOrder.Descending],
includeItemTypes: [BaseItemKind.Audio]
},
cardOptions: {
showUnplayedIndicator: false,
shape: 'overflowSquare',
showTitle: true,
showParentTitle: true,
action: 'instantmix',
lazy: true,
centerText: true,
overlayMoreButton: true,
cardLayout: false,
coverImage: true
}
},
{
name: 'HeaderFrequentlyPlayed',
type: 'Audio',
view: SectionsView.FrequentlyPlayedMusic,
parametersOptions: {
sortBy: [ItemSortBy.PlayCount],
sortOrder: [SortOrder.Descending],
includeItemTypes: [BaseItemKind.Audio]
},
cardOptions: {
showUnplayedIndicator: false,
shape: 'overflowSquare',
showTitle: true,
showParentTitle: true,
action: 'instantmix',
lazy: true,
centerText: true,
overlayMoreButton: true,
cardLayout: false,
coverImage: true
}
}
];
};
interface SuggestionsItemsContainerProps {
parentId?: string | null;
sectionsView: SectionsView[];
}
const SuggestionsItemsContainer: FC<SuggestionsItemsContainerProps> = ({
parentId,
sectionsView
}) => {
const suggestionsSections = getSuggestionsSections();
return (
<>
{suggestionsSections
.filter((section) => sectionsView.includes(section.view))
.map((section) => (
<SuggestionsSectionContainer
key={section.view}
parentId={parentId}
section={section}
/>
))}
</>
);
};
export default SuggestionsItemsContainer;

View file

@ -0,0 +1,49 @@
import React, { FC } from 'react';
import { useGetItemsBySectionType } from 'hooks/useFetchItems';
import globalize from 'scripts/globalize';
import Loading from 'components/loading/LoadingComponent';
import { appRouter } from 'components/router/appRouter';
import SectionContainer from './SectionContainer';
import { Sections } from 'types/suggestionsSections';
interface SuggestionsSectionContainerProps {
parentId?: string | null;
section: Sections;
}
const SuggestionsSectionContainer: FC<SuggestionsSectionContainerProps> = ({
parentId,
section
}) => {
const getRouteUrl = () => {
return appRouter.getRouteUrl('list', {
serverId: window.ApiClient.serverId(),
itemTypes: section.type,
parentId: parentId
});
};
const { isLoading, data: items } = useGetItemsBySectionType(
section,
parentId
);
if (isLoading) {
return <Loading />;
}
return (
<SectionContainer
sectionTitle={globalize.translate(section.name)}
items={items || []}
url={getRouteUrl()}
cardOptions={{
...section.cardOptions
}}
/>
);
};
export default SuggestionsSectionContainer;