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

Refactoring Suggestions View

This commit is contained in:
grafixeyehero 2022-09-02 02:46:05 +03:00
parent cf137497a0
commit 9d88af3dfe
5 changed files with 116 additions and 158 deletions

View file

@ -1,13 +1,9 @@
import '../../elements/emby-itemscontainer/emby-itemscontainer';
import { RecommendationDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import React, { FunctionComponent, useEffect, useRef } from 'react';
import React, { FunctionComponent } from 'react';
import cardBuilder from '../../components/cardbuilder/cardBuilder';
import globalize from '../../scripts/globalize';
import ItemsContainerElement from '../../elements/ItemsContainerElement';
import ItemsScrollerContainerElement from '../../elements/ItemsScrollerContainerElement';
import escapeHTML from 'escape-html';
import SectionContainer from './SectionContainer';
type RecommendationContainerProps = {
getPortraitShape: () => string;
@ -16,8 +12,6 @@ type RecommendationContainerProps = {
}
const RecommendationContainer: FunctionComponent<RecommendationContainerProps> = ({ getPortraitShape, enableScrollX, recommendation = {} }: RecommendationContainerProps) => {
const element = useRef<HTMLDivElement>(null);
let title = '';
switch (recommendation.RecommendationType) {
@ -40,40 +34,15 @@ const RecommendationContainer: FunctionComponent<RecommendationContainerProps> =
break;
}
useEffect(() => {
cardBuilder.buildCards(recommendation.Items || [], {
itemsContainer: element.current?.querySelector('.itemsContainer'),
return <SectionContainer
sectionTitle={escapeHTML(title)}
enableScrollX={enableScrollX}
items={recommendation.Items || []}
cardOptions={{
shape: getPortraitShape(),
scalable: true,
overlayPlayButton: true,
allowBottomPadding: true,
showTitle: true,
showYear: true,
centerText: true
});
}, [enableScrollX, getPortraitShape, recommendation]);
return (
<div ref={element}>
<div className='verticalSection'>
<div className='sectionTitleContainer sectionTitleContainer-cards'>
<h2 className='sectionTitle sectionTitle-cards padded-left'>
{escapeHTML(title)}
</h2>
</div>
{enableScrollX() ? <ItemsScrollerContainerElement
scrollerclassName='padded-top-focusscale padded-bottom-focusscale'
dataMousewheel='false'
dataCenterfocus='true'
className='itemsContainer scrollSlider focuscontainer-x'
/> : <ItemsContainerElement
className='itemsContainer focuscontainer-x padded-left padded-right vertical-wrap'
/>}
</div>
</div>
);
showYear: true
}}
/>;
};
export default RecommendationContainer;

View file

@ -1,60 +0,0 @@
import '../../elements/emby-itemscontainer/emby-itemscontainer';
import { BaseItemDtoQueryResult } from '@thornbill/jellyfin-sdk/dist/generated-client';
import React, { FunctionComponent, useEffect, useRef } from 'react';
import cardBuilder from '../../components/cardbuilder/cardBuilder';
import globalize from '../../scripts/globalize';
import ItemsContainerElement from '../../elements/ItemsContainerElement';
import ItemsScrollerContainerElement from '../../elements/ItemsScrollerContainerElement';
type ResumableItemsContainerProps = {
getThumbShape: () => string;
enableScrollX: () => boolean;
itemsResult?: BaseItemDtoQueryResult;
}
const ResumableItemsContainer: FunctionComponent<ResumableItemsContainerProps> = ({ getThumbShape, enableScrollX, itemsResult = {} }: ResumableItemsContainerProps) => {
const element = useRef<HTMLDivElement>(null);
useEffect(() => {
const allowBottomPadding = !enableScrollX();
cardBuilder.buildCards(itemsResult.Items || [], {
itemsContainer: element.current?.querySelector('.itemsContainer'),
parentContainer: element.current?.querySelector('#resumableSection'),
preferThumb: true,
shape: getThumbShape(),
scalable: true,
overlayPlayButton: true,
allowBottomPadding: allowBottomPadding,
cardLayout: false,
showTitle: true,
showYear: true,
centerText: true
});
}, [enableScrollX, getThumbShape, itemsResult.Items]);
return (
<div ref={element}>
<div id='resumableSection' className='verticalSection hide'>
<div className='sectionTitleContainer sectionTitleContainer-cards'>
<h2 className='sectionTitle sectionTitle-cards padded-left'>
{globalize.translate('HeaderContinueWatching')}
</h2>
</div>
{enableScrollX() ? <ItemsScrollerContainerElement
scrollerclassName='padded-top-focusscale padded-bottom-focusscale'
dataMousewheel='false'
dataCenterfocus='true'
className='itemsContainer scrollSlider focuscontainer-x'
/> : <ItemsContainerElement
className='itemsContainer focuscontainer-x padded-left padded-right vertical-wrap'
/>}
</div>
</div>
);
};
export default ResumableItemsContainer;

View file

@ -4,39 +4,44 @@ import { BaseItemDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
import React, { FunctionComponent, useEffect, useRef } from 'react';
import cardBuilder from '../../components/cardbuilder/cardBuilder';
import globalize from '../../scripts/globalize';
import ItemsContainerElement from '../../elements/ItemsContainerElement';
import ItemsScrollerContainerElement from '../../elements/ItemsScrollerContainerElement';
import { ICardOptions } from './type';
type RecentlyAddedItemsContainerProps = {
getPortraitShape: () => string;
type SectionContainerProps = {
sectionTitle: string;
enableScrollX: () => boolean;
items?: BaseItemDto[];
cardOptions?: ICardOptions;
}
const RecentlyAddedItemsContainer: FunctionComponent<RecentlyAddedItemsContainerProps> = ({ getPortraitShape, enableScrollX, items = [] }: RecentlyAddedItemsContainerProps) => {
const SectionContainer: FunctionComponent<SectionContainerProps> = ({
sectionTitle,
enableScrollX,
items = [],
cardOptions = {}
}: SectionContainerProps) => {
const element = useRef<HTMLDivElement>(null);
useEffect(() => {
cardBuilder.buildCards(items, {
itemsContainer: element.current?.querySelector('.itemsContainer'),
parentContainer: element.current?.querySelector('#recentlyAddedItemsSection'),
shape: getPortraitShape(),
parentContainer: element.current?.querySelector('.verticalSection'),
scalable: true,
overlayPlayButton: true,
allowBottomPadding: true,
showTitle: true,
showYear: true,
centerText: true
centerText: true,
cardLayout: false,
...cardOptions
});
}, [enableScrollX, getPortraitShape, items]);
}, [cardOptions, enableScrollX, items]);
return (
<div ref={element}>
<div id='recentlyAddedItemsSection' className='verticalSection hide'>
<div className='verticalSection hide'>
<div className='sectionTitleContainer sectionTitleContainer-cards'>
<h2 className='sectionTitle sectionTitle-cards padded-left'>
{globalize.translate('HeaderLatestMovies')}
{sectionTitle}
</h2>
</div>
@ -54,4 +59,4 @@ const RecentlyAddedItemsContainer: FunctionComponent<RecentlyAddedItemsContainer
);
};
export default RecentlyAddedItemsContainer;
export default SectionContainer;

View file

@ -10,7 +10,40 @@ export type IQuery = {
StartIndex: number;
ParentId?: string | null;
IsFavorite?: boolean;
IsMissing?: boolean;
Limit:number;
NameLessThan?: string;
NameStartsWith?: string;
}
export type ICardOptions = {
itemsContainer?: HTMLElement;
parentContainer?: HTMLElement;
allowBottomPadding?: boolean;
centerText?: boolean;
coverImage?: boolean;
inheritThumb?: boolean;
overlayMoreButton?: boolean;
overlayPlayButton?: boolean;
overlayText?: boolean;
preferThumb?: boolean;
scalable?: boolean;
shape?: string;
lazy?: boolean;
cardLayout?: boolean;
showParentTitle?: boolean;
showParentTitleOrTitle?: boolean;
showAirTime?: boolean;
showAirDateTime?: boolean;
showChannelName?: boolean;
showTitle?: boolean;
showYear?: boolean;
showDetailsMenu?: boolean;
missingIndicator?: boolean;
showLocationTypeIndicator?: boolean;
showSeriesYear?: boolean;
showUnplayedIndicator?: boolean;
showChildCountIndicator?: boolean;
lines?: number;
context?: string;
}

View file

@ -5,9 +5,8 @@ import layoutManager from '../../components/layoutManager';
import loading from '../../components/loading/loading';
import dom from '../../scripts/dom';
import globalize from '../../scripts/globalize';
import RecentlyAddedItemsContainer from '../components/RecentlyAddedItemsContainer';
import RecommendationContainer from '../components/RecommendationContainer';
import ResumableItemsContainer from '../components/ResumableItemsContainer';
import SectionContainer from '../components/SectionContainer';
type IProps = {
topParentId: string | null;
@ -15,7 +14,7 @@ type IProps = {
const SuggestionsView: FunctionComponent<IProps> = (props: IProps) => {
const [ latestItems, setLatestItems ] = useState<BaseItemDto[]>([]);
const [ resumeItemsResult, setResumeItemsResult ] = useState<BaseItemDtoQueryResult>();
const [ resumeResult, setResumeResult ] = useState<BaseItemDtoQueryResult>({});
const [ recommendations, setRecommendations ] = useState<RecommendationDto[]>([]);
const element = useRef<HTMLDivElement>(null);
@ -37,6 +36,31 @@ const SuggestionsView: FunctionComponent<IProps> = (props: IProps) => {
});
}, []);
const loadResume = useCallback((page, userId, parentId) => {
loading.show();
const screenWidth = dom.getWindowSize().innerWidth;
const options = {
SortBy: 'DatePlayed',
SortOrder: 'Descending',
IncludeItemTypes: 'Movie',
Filters: 'IsResumable',
Limit: screenWidth >= 1600 ? 5 : 3,
Recursive: true,
Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo',
CollapseBoxSetItems: false,
ParentId: parentId,
ImageTypeLimit: 1,
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
EnableTotalRecordCount: false
};
window.ApiClient.getItems(userId, options).then(result => {
setResumeResult(result);
loading.hide();
autoFocus(page);
});
}, [autoFocus]);
const loadLatest = useCallback((page: HTMLDivElement, userId: string, parentId: string | null) => {
const options = {
IncludeItemTypes: 'Movie',
@ -50,43 +74,16 @@ const SuggestionsView: FunctionComponent<IProps> = (props: IProps) => {
window.ApiClient.getJSON(window.ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(items => {
setLatestItems(items);
// FIXME: Wait for all sections to load
autoFocus(page);
});
}, [autoFocus]);
const loadResume = useCallback((page, userId, parentId) => {
loading.show();
const screenWidth = dom.getWindowSize();
const options = {
SortBy: 'DatePlayed',
SortOrder: 'Descending',
IncludeItemTypes: 'Movie',
Filters: 'IsResumable',
Limit: screenWidth.innerWidth >= 1600 ? 5 : 3,
Recursive: true,
Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo',
CollapseBoxSetItems: false,
ParentId: parentId,
ImageTypeLimit: 1,
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
EnableTotalRecordCount: false
};
window.ApiClient.getItems(userId, options).then(result => {
setResumeItemsResult(result);
loading.hide();
// FIXME: Wait for all sections to load
autoFocus(page);
});
}, [autoFocus]);
const loadSuggestions = useCallback((page, userId) => {
const screenWidth = dom.getWindowSize();
const screenWidth = dom.getWindowSize().innerWidth;
let itemLimit = 5;
if (screenWidth.innerWidth >= 1600) {
if (screenWidth >= 1600) {
itemLimit = 8;
} else if (screenWidth.innerWidth >= 1200) {
} else if (screenWidth >= 1200) {
itemLimit = 6;
}
const url = window.window.ApiClient.getUrl('Movies/Recommendations', {
@ -100,7 +97,6 @@ const SuggestionsView: FunctionComponent<IProps> = (props: IProps) => {
window.ApiClient.getJSON(url).then(result => {
setRecommendations(result);
// FIXME: Wait for all sections to load
autoFocus(page);
});
}, [autoFocus]);
@ -126,11 +122,27 @@ const SuggestionsView: FunctionComponent<IProps> = (props: IProps) => {
return (
<div ref={element}>
<ResumableItemsContainer getThumbShape={getThumbShape} enableScrollX={enableScrollX} itemsResult={resumeItemsResult} />
<SectionContainer
sectionTitle={globalize.translate('HeaderContinueWatching')}
enableScrollX={enableScrollX}
items={resumeResult.Items || []}
cardOptions={{
preferThumb: true,
shape: getThumbShape(),
showYear: true
}}
/>
<RecentlyAddedItemsContainer getPortraitShape={getPortraitShape} enableScrollX={enableScrollX} items={latestItems} />
<SectionContainer
sectionTitle={globalize.translate('HeaderLatestMovies')}
enableScrollX={enableScrollX}
items={latestItems}
cardOptions={{
shape: getPortraitShape(),
showYear: true
}}
/>
<div id='recommendations'>
{!recommendations.length ? <div className='noItemsMessage centerMessage'>
<h1>{globalize.translate('MessageNothingHere')}</h1>
<p>{globalize.translate('MessageNoMovieSuggestionsAvailable')}</p>
@ -138,7 +150,6 @@ const SuggestionsView: FunctionComponent<IProps> = (props: IProps) => {
return <RecommendationContainer key={index} getPortraitShape={getPortraitShape} enableScrollX={enableScrollX} recommendation={recommendation} />;
})}
</div>
</div>
);
};